[.NET] 使用 Reflection 呼叫泛型方法

本文會介紹如何使用 Reflection 來呼叫與存取類別中的泛型方法。

只要是使用 .NET 2.0 或更新版本的平台開發應用程式的開發人員,一定都會知道泛型 (Generic Type) 這個東西吧,泛型讓許多集合類別可以將型別鎖定在一個特定的類別中,代表泛型物件內含的是具有強型別 (strong-typed) 的特性,而不用像以前 .NET 1.x (Generic Type 是 .NET 2.0 才有的) 的 ArrayList 還要做型別的轉換 (boxing and unboxing),熟知 .NET Framework 內部運作的人都知道,這種 object 和特定型別的轉換是會耗時間的,小量資料可能無影響,但若資料是數十萬以上時,效能可能就會很明顯的降低。

下列說明是取自 MSDN: http://msdn.microsoft.com/zh-tw/library/ms173196.aspx

image

所以如果你還不知道或未曾用過泛型集合物件,就趕快去學吧,它很簡單易用,但卻有強大的資料處理功能。

會用還不夠,既然泛型這麼好用,那當然要把它引入我們的物件設計,例如下列程式碼即為一個典型的泛型類別,內含三個泛型方法:

public class ExampleClass2<T>
{
    public void ExampleMethod1<U>() where U : class
    {
        Console.WriteLine("Class: {0} in Generic Class with {1}", typeof(U).FullName, typeof(T).FullName);
    }

    public void ExampleMethod2<T1, T2>(T1 value1, T2 value2)
        where T1 : struct
        where T2 : struct
    {
        int v1 = Convert.ToInt32(value1);
        int v2 = Convert.ToInt32(value2);

        Console.WriteLine("T1 + T2 = {0}", v1 + v2);
    }

    public void ExampleMethod3<U>(string Data) where U : class
    {
        Console.WriteLine("Class: {0} in Generic Class with {1}, Data: {2}", typeof(U).FullName, typeof(T).FullName, Data);
    }

    public U ExampleMethod4<U>(string Data)
    {
        return (U)Activator.CreateInstance(typeof(U));
    }
}

 

呼叫的程式碼也是簡單到不行:

 

ExampleClass<int> exampleClass = new ExampleClass<int>();
exampleClass.ExampleMethod1<int>();
exampleClass.ExampleMethod2<int, int>(1, 2);
exampleClass.ExampleMethod3<int>(“Data is here”);

 

不過,通常具有彈性的程式碼不會這麼簡單,而且會經常使用到 Reflection 這個技術,例如你可能會寫一支共用的程式,而所有的呼叫資料都保存在 XML 檔案中,需要時由 XML 讀取資料 (包含要使用的型別),再交由 Reflection 去動態生成物件並自動呼叫方法,以取得最終的結果。而 Reflection 大多數都是用在非泛型的物件與方法,若是想要在泛型中使用 Reflection,就要做一點特別的處理:

1. 使用 Reflection 先取得方法的定義。
2. 使用 MethodInfo.MakeGenericMethod() 將方法轉換成具有指定型別參數的 MethodInfo 物件。
3. 使用 MethodInfo.Invoke() 呼叫方法,並傳入必要參數。

例如下列程式碼 (ExampleClass 的宣告請參考前面的 ExampleClass 程式碼):

ExampleClass exampleClass = new ExampleClass();

MethodInfo exampleMethod1Instance = exampleClass.GetType().GetMethod("ExampleMethod1", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public);
MethodInfo exampleMethod2Instance = exampleClass.GetType().GetMethod("ExampleMethod2", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public);
MethodInfo exampleMethod3Instance = exampleClass.GetType().GetMethod("ExampleMethod3", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public);

if (exampleMethod1Instance.IsGenericMethod)
    Console.WriteLine("ExampleMethod1 is a generic method.");
if (exampleMethod2Instance.IsGenericMethod)
    Console.WriteLine("ExampleMethod2 is a generic method.");
if (exampleMethod3Instance.IsGenericMethod)
    Console.WriteLine("ExampleMethod3 is a generic method.");

MethodInfo exampleMethod1GenericInstance = exampleMethod1Instance.MakeGenericMethod(typeof(TestClass));
MethodInfo exampleMethod2GenericInstance = exampleMethod2Instance.MakeGenericMethod(typeof(int), typeof(int));
MethodInfo exampleMethod3GenericInstance = exampleMethod3Instance.MakeGenericMethod(typeof(TestClass));

exampleMethod1GenericInstance.Invoke(exampleClass, null);
exampleMethod2GenericInstance.Invoke(exampleClass, new object[] { 256, 256 });
exampleMethod3GenericInstance.Invoke(exampleClass, new object[] { "Data is here" });

 

其執行的結果為:

image

 

使用 MakeGenericMethod() 的原因,是因為泛型在執行時,一定要有一個型別的資訊,若沒有型別的資訊,在執行時會發生問題,因此必須要先用 MakeGenericMethod() 將型別傳入,以建構出具有型別的方法物件資訊,才可以進行 Reflection 的呼叫使用。

而若是要呼叫可以生成特定型別的泛型方法,你可以參考 ExampleMethod4<U>(string Data) 這個函式的作法,雖然它是很簡單的函式,但它可以生成 U 型別的執行個體,適合於使用 XML 或資料庫進行物件定義的設計需求。

不過目前 Reflection 似乎有一個缺陷,就是它無法動態生成泛型類別 (Generic Class),如果使用下列程式碼生成泛型類別:

Type exampleClass = Type.GetType(“ExampleClass”);
Type exampleClass = Type.GetType(“ExampleClass<>”);

你會得到 null 的結果,若是指定 throwOnError = true 時,則會得到找不到型別的例外。所以如果要使用泛型類別的話,只能在程式碼中先固定宣告,然後使用 Type.MakeGenericType() 來生成泛型類別的定義,最後再用 Activator.CreateInstance() 來產生物件執行個體,例如:

Type exampleClassTypeDef = typeof(ExampleClass2<>);
exampleClassTypeDef = exampleClassTypeDef.MakeGenericType(typeof(ExampleClass));

object exampleClass = Activator.CreateInstance(exampleClassTypeDef);

...

 

Reference:

http://blogs.microsoft.co.il/blogs/gilf/archive/2008/10/10/invoking-generic-methods-with-reflection.aspx
http://msdn.microsoft.com/zh-tw/library/ms173128(v=VS.90).aspx