C#反射

2023-05-01 18:00:08

C#反射簡介

反射(Reflection)是C#語言中一種非常有用的機制,它可以在執行時動態獲取物件的型別資訊並且進行相應的操作。反射是一種在.NET Framework中廣泛使用的技術,它是實現上述特性的基礎,非常重要。

反射能幹什麼?

使用反射可以讓我們在執行時動態地獲取物件的型別資訊並進行相應的操作,比如建立物件、呼叫方法、獲取屬性等。舉個簡單的例子,我們在寫程式碼時,為了能夠呼叫某個物件的方法,我們通常需要先建立這個物件的範例,然後才能呼叫其方法。而使用反射機制,我們可以在執行時動態地建立物件並直接呼叫其方法,而不必提前定義它們。

反射的基本使用

反射的核心是Type類,它表示.NET Framework中的型別,即類、結構體、列舉等。我們可以使用Type類來獲取程式集中定義的型別,獲取型別的成員,建立型別的範例等等。下面我們舉幾個反射的基本使用案例。

1. 獲取型別資訊

獲取型別資訊是反射最基本的用法之一,我們可以使用Type類的靜態方法GetType獲取型別資訊,如下所示。

using System;
namespace ReflectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(string);
            Console.WriteLine(type.FullName);
            Console.ReadKey();
        }
    }
}

這個例子中,我們獲取了string型別的Type物件,然後輸出了這個物件的FullName屬性,也就是string型別的完全限定名稱System.String。

2. 反射建立物件

使用反射可以在執行時動態地建立物件,這極大地方便了我們的程式設計工作。例如,我們通常要編寫一個工廠類來根據不同的型別建立不同的物件,而使用反射則可以在不需要工廠類的情況下建立物件。下面是一個簡單的例子。

using System;
using System.Reflection;

namespace ReflectionDemo
{
    class MyClass
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 獲取 MyClass 的型別物件
            Type myClassType = typeof(MyClass);

            // 建立 MyClass 型別的範例
            MyClass myClass = (MyClass)Activator.CreateInstance(myClassType);

            // 設定物件屬性值
            PropertyInfo propId = myClassType.GetProperty("Id");
            propId.SetValue(myClass, 100);

            PropertyInfo propName = myClassType.GetProperty("Name");
            propName.SetValue(myClass, "Tom");

            // 列印物件屬性值
            Console.WriteLine(myClass.Id);
            Console.WriteLine(myClass.Name);

            Console.ReadLine();
        }
    }
}

上述程式碼中,我們首先獲取了 MyClass 型別的物件,然後呼叫 Activator.CreateInstance 方法來建立該型別的範例。接著,我們利用 PropertyInfo 物件獲取、設定物件的屬性值,最後列印屬性值。以上就是用反射機制在 C# 中建立物件的過程。

3. 反射呼叫方法

使用反射可以在執行時動態地呼叫物件的方法。我們可以使用MethodInfo類來獲取方法資訊,然後呼叫MethodInfo.Invoke方法來呼叫這個方法,如下所示。

using System;
using System.Reflection;
namespace ReflectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(string);
            MethodInfo method = type.GetMethod("ToUpper", new Type[] { });
            string result = (string)method.Invoke("Hello World", null);
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

這個例子中,我們獲取了string型別的ToUpper方法資訊,然後使用Invoke方法呼叫這個方法,將字串"Hello World"轉化為大寫輸出。

反射的高階用法

反射的高階用法是指使用反射來實現更高階的程式設計功能,比如泛型、LINQ等。下面我們舉幾個例子展示反射的高階用法。

1. 獲取泛型方法資訊

使用反射可以在執行時動態地獲取泛型方法的資訊,然後在執行時構造泛型型別。下面是一個例子。

 
using System;
using System.Reflection;
namespace ReflectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(Program);
            MethodInfo method = type.GetMethod("TestMethod");
            MethodInfo genericMethod = method.MakeGenericMethod(typeof(string));
            genericMethod.Invoke(null, null);
            Console.ReadKey();
        }
        public static void TestMethod<T>()
        {
            Console.WriteLine(typeof(T).FullName);
        }
    }
}

這個例子中,我們使用GetMethod方法獲取了TestMethod方法資訊,然後使用MakeGenericMethod方法構造了泛型方法,並將其轉化為MethodInfo類進行輸出。

2. 在執行時構造LINQ查詢

使用反射可以在執行時動態地根據查詢條件構造LINQ查詢。下面是一個例子。

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ReflectionDemo
{
    class MyEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 構造查詢條件
            string fieldName = "Id";
            int fieldValue = 100;

            // 獲取執行時型別和欄位資訊
            Type entityType = typeof(MyEntity);
            PropertyInfo property = entityType.GetProperty(fieldName);

            // 使用表示式樹構造查詢條件
            ParameterExpression parameter = Expression.Parameter(entityType, "x");
            MemberExpression member = Expression.Property(parameter, property);
            ConstantExpression constant = Expression.Constant(fieldValue, property.PropertyType);
            BinaryExpression equal = Expression.Equal(member, constant);
            Expression<Func<MyEntity, bool>> expression = Expression.Lambda<Func<MyEntity, bool>>(equal, parameter);

            // 執行查詢
            IQueryable<MyEntity> entities = new List<MyEntity>
            {
                new MyEntity { Id = 100, Name = "Alice" },
                new MyEntity { Id = 200, Name = "Bob" },
                new MyEntity { Id = 300, Name = "Charlie" },
                new MyEntity { Id = 400, Name = "David" },
            }.AsQueryable();
            IQueryable<MyEntity> query = entities.Where(expression);

            // 輸出查詢結果
            foreach (MyEntity entity in query)
            {
                Console.WriteLine($"Id={entity.Id}, Name={entity.Name}");
            }

            Console.ReadLine();
        }

        static object CreateWhereLambda(Type elementType)
        {
            MethodInfo whereMethod = typeof(Program).GetMethod(nameof(CreateWhereLambdaImpl), BindingFlags.NonPublic | BindingFlags.Static);
            return whereMethod.MakeGenericMethod(elementType).Invoke(null, null);
        }

        static Func<T, bool> CreateWhereLambdaImpl<T>()
        {
            return item => (int)(object)item % 2 == 0;
        }
    }
}

在上述範例中,我們首先定義了一個查詢條件,然後獲取了執行時型別和欄位資訊,接著使用表示式樹構造了查詢條件,並利用反射執行了 LINQ 查詢。最終,我們輸出的結果只包括 Id 等於 100 的實體。

反射使用的注意事項

使用反射需要格外注意效能和安全問題,一些常見的注意事項包括:

  1. 儘量使用已經編譯好的程式集,避免使用動態編譯的程式集。
  2. 反射的效能較低,儘量少用。
  3. 反射有漏洞,應注意安全問題。
  4. 授權可以防止反射的濫用,應根據實際情況授權反射使用許可權。

總結

通過本文的學習,我們瞭解了反射的基本概念和使用方法,並且掌握了反射的高階用法。反射在C#中是一項非常強大且必要的技術,如果恰當地使用它,可以使我們的程式設計工作變得更加高效和便捷。同時,我們也需要格外注意反射使用過程中的效能和安全問題,做好樣本授權等工作,以便更好地使用反射這個強大的功能。