動態泛型

泛型的 T 並不能由變數的型別帶入,所以在編寫程式碼的時候會需要指定強型別,要做到動態泛型也會有一點難度

 最近有一個需求是需要把傳入的物件陣列(int, float, string...),並根據他們的型別轉成 List<T>

程式碼看起來會像是這樣

            var cls = new ClsTest();
            ArrayList array = new ArrayList();
            array.Add(123);
            array.Add("name");
            foreach (var arrObj in array)
            {
                var objType = arrObj.GetType();
                var result = cls.MakeList<objType>(); //error
            }
    class ClsTest
    {
        //case 1
        public IEnumerable<T> MakeList<T>()
        {
            return new List<T>();
        }

        //case 2
        public static string StaticDisplay()
        {
            return "OK";
        }

        //case 3
        public IEnumerable<T> Read<T>(bool isAddDefault = true)
        {
            var result = new List<T>();

            if (isAddDefault)
                result.Add(default(T));

            return result;
        }

        public IEnumerable<T> Read<T>(string name, bool isAddDefault = true)
        {
            return Read<T>(isAddDefault);
        }
    }

            
然而在cls.MakeList<objType>()中卻不允許我們丟入變數當成 type,即便這個變數是個 type

這個時候我們就需要做到「動.態.泛.型」的事情了

為了達到這個目地,可以使用 反射 與 MakeGenericMethod 的方法達到
GetInvoke 第一個參數需填入 instance 的參數, 第二個參數則是填入輸入參數的值,因為這個方法不需傳入參數,所以這裡放 null 即可

            var cls = new ClsTest();
            MethodInfo method = cls.GetType().GetMethod("MakeList");
            foreach (var arrObj in array)
            {
                var objType = arrObj.GetType();
                MethodInfo generic = method.MakeGenericMethod(objType);
                var resultPara = generic.Invoke(cls, null); //需有實體物件 cls,沒有參數放 null
                Console.WriteLine(resultPara.GetType());
            }


另外如果呼叫的是 static method 的話,做法又有一點不同
GetInvoke 第一個參數就不用填入 instance 的參數,因為 static method 是不需要實體化的

            MethodInfo method = typeof(ClsTest).GetMethod("StaticDisplay");
            var resultPara = method.Invoke(null, null); //不需實體化 與參數,都放null
            Console.WriteLine(resultPara.GetType());


但如果今天你的 method 需要有多載的功能,並不是那麼單純根據 method name 就可以抓出來的話,就像 ClsTest.Read 這個方法
提供了 Generic 的方法,而且又有多種參數的入口這時候就需要用 GetMethods 的方法來進行過瀘, 以下僅展示傳回 List<int>,並預設帶回一組 default<T> 的資料
 

            var cls = new ClsTest();
            var methods = cls.GetType().GetMethods().Where(m => m.Name == "Read"
                && m.IsGenericMethod
                && m.GetParameters().Count() == 1
                )
                .ToList();

            MethodInfo method = methods.First();
            MethodInfo generic = method.MakeGenericMethod(typeof(int));
            var resultPara = generic.Invoke(cls, new object[] { true }); //即便方法裡的參數是 default parameter,並不代表我們可以不丟參數進去
            Console.WriteLine(resultPara.GetType());

 

參考文件

https://docs.microsoft.com/zh-tw/dotnet/api/system.type.getmethod?view=netframework-4.8

https://dotblogs.com.tw/regionbbs/2011/02/09/invoke_generic_methods