C#反射與特性(四):實例化類型


前面三篇文章,介紹了使用程序集、獲取 Type 類型、使用 Type 類型獲取成員信息。

通過前面的學習,我們大概了解到 Assembly、PropertyInfo、FieldInfo、ConstructorInfo、MethodInfo、ParameterInfo、EventInfo、MemberInfo 的存在和輸出信息。

從本篇文章,將開始通過反射實例化類型,進行一系列的反射操作實踐。

本篇文章,主要講述實例化類型、實例化委托。

1,實例化類型

從類型(Type)創建實例對象的方式,有兩種

  • Activator.CreateInstance() 方法 ,操作 類型 Type
  • ConstructorInfo.Invoke(),操作 構造函 ConstructorInfo

實例化一個類型時,首先考慮類型的構造函數。

1.1 Activator.CreateInstance()

首先,在 Microsoft Docs 中,這么定義:

使用與指定參數匹配程度最高的構造函數創建指定類型的實例。

這是什么意思呢?

我們來看一下 Activator.CreateInstance() 最常用的兩個個重載。

object? CreateInstance(Type type);
object? CreateInstance(Type type, params object[] args);

args 就是實例化類型時,給構造函數傳遞的參數。因為使用的是 object ,最終實例化是使用到的 構造函數 是 區配程度 最高的。

好了,不扯了,我們來實踐一下。

1.1.1 簡單類型

            Type typeA = typeof(int);
            object objA = Activator.CreateInstance(typeA);

通過上面的代碼,我們可以很方便的實例化一個簡單類型。

當然,你可以看到,創建后的類型是 object 。

那么,問題來了

反射后,少不得一頓裝箱拆箱了。

目前來說,我們使用不了 int 的方法了,只能使用 object 。怎么辦?

先留着后面再解決唄。

當然,可以直接使用 int ,那我還使用反射干嘛?

int i = 666;

這樣不就行了?

如果需要在程序生成后,引用 dll 的代碼,我們可以這樣做

            Assembly ass = Assembly.LoadFrom(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\System.Runtime.dll");
            Type typeA = ass.GetType("System.Int32");
            object objA = Activator.CreateInstance(typeA);

1.1.2 簡單類型的構造函數

對於 int 這些簡單類型,沒有別的操作,直接實例化就行,這里例舉 DateTime 類型,通過不同的參數,調用構造函數去實例化。

            Type typeA = typeof(DateTime);
            object objA = Activator.CreateInstance(typeA,2020,1,5);

當然,如果無法找到合適的構造函數來實例化類型,則會彈出 System.MissingMethodException 異常。

1.1.3 object

讓我們創建一個類型

        public MyClass(object a, object b)
        {

        }

        public MyClass(string a, string b)
        {

        }
        public MyClass(string a, object b)
        {

        }

通過反射創建實例

            Type typeA = typeof(MyClass);
            object objA = Activator.CreateInstance(typeA, 2020,666);

            Console.WriteLine(typeA.Name);

以上代碼並不會報錯。

原因有兩個,① 類型轉換至 object,會攜帶原類型的信息;② Activator.CreateInstance() 會尋找最優的構造函數。

所以上面創建實例化時,會調用 public MyClass(int a, int b)。沒有符合的怎么辦?那就調用最優解;

所以上面這點小意思,不會造成任何影響的。

對於簡單類型,尋找過程如下

1,尋找相應類型的構造函數

Activator.CreateInstance(typeA, 2020,666),2020 是 typeo(int),666 是 typeof(int)。

最優是 public MyClass(int a, int b)

2,找不到的話,就找可以隱式轉換的構造函數

例如 int -> long;

public MyClass(long a, long b)

3,如果沒有隱式轉換,則 object

public MyClass(object a, object b)

如果都沒有符合條件的話,只能報錯了;

驗證一下

    public class MyClass
    {
        public MyClass(string a, string b) { }
        public MyClass(int a, int b) { }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Type typeA = typeof(MyClass);
            long a = 666;
            long b = 666;

            object objA = Activator.CreateInstance(typeA, a, b);

            Console.WriteLine(typeA.Name);
            Console.ReadKey();
        }
    }

不出意外的話,上面代碼會報錯。

1.1.4 故意出錯

    public class MyClass
    {
        public MyClass(string a = null) { }
        public MyClass(StringBuilder a = null) { }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Type typeA = typeof(MyClass);

            object objA = Activator.CreateInstance(typeA, null);

            Console.WriteLine(typeA.Name);
            Console.ReadKey();
            Console.ReadKey();
        }
    }

如無意外,上面的代碼,執行后會報錯。

因為當實例化時,參數為 null,有兩個符合要求的構造函數。

其它情況下,根據 1.1.3 中,尋找構造函數的步驟,可以大致判斷是否會出錯。

1.1.5 Activator.CreateInstance() 性能

我們來通過正常的代碼實例化一個類型,實聲明並且賦值,共 1 千萬次。

            Stopwatch time = new Stopwatch();
            time.Start();
            for (int i = 0; i < 1_000_0000; i++)
            {
                int a = 666;
            }
            time.Stop();
            Console.WriteLine(time.ElapsedMilliseconds);
            time.Reset();
            time.Restart();
            for (int i = 0; i < 1_000_0000; i++)
            {
                int a = 666;
            }
            time.Stop();
            Console.WriteLine(time.ElapsedMilliseconds);

時間

24
23

使用反射

            Type typeA = typeof(int);
            Stopwatch time = new Stopwatch();
            time.Start();

            for (int i = 0; i < 1_000_0000; i++)
            {
                object objA = Activator.CreateInstance(typeA);
            }
            time.Stop();
            Console.WriteLine(time.ElapsedMilliseconds);
            time.Reset();
            time.Restart();
            for (int i = 0; i < 1_000_0000; i++)
            {
                object objA = Activator.CreateInstance(typeA);
            }
            time.Stop();
            Console.WriteLine(time.ElapsedMilliseconds);

時間

589
504

500 / 25 = 20,沒錯,性能相差了 20倍以上。

1.2 ConstructorInfo.Invoke()

ConstructorInfo.Invoke() 調用構造函數的限制性比Activator.CreateInstance() 高,並且是嚴格對應的。

1.1.4 中,故意出錯的代碼中,可以看到因為 null 時,有多個構造函數符合條件而導致程序報錯。

使用 ConstructorInfo.Invoke() 創建實例進行測試。

    public class MyClass
    {
        public MyClass(string a = null) { Console.WriteLine(6666); }
        public MyClass(StringBuilder a = null) { }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // 通過唯一性獲取構造函數
            // 通過參數類型和數量,獲取到唯一的構造函數
            ConstructorInfo conStruct = typeof(MyClass).GetConstructor(new Type[] { typeof(string) });

            // 傳輸參數值並且進行實例化
            object objA = conStruct.Invoke(new object[] { null });

            Console.ReadKey();
        }
    }

使用 typeof(MyClass).GetConstructor(new Type[] { typeof(string) }); 獲取到類型的構造函數,然后使用 ConstructorInfo.Invoke() 實例化。

上面 GetConstructor() 的方法,重載定義如下

public ConstructorInfo? GetConstructor(Type[] types);

通過什么的方法,可以使用 public 構造函數實例化一個類型,如果想調用非 public 的構造函數呢?

可以使用 BindingFlags,這些后面再慢慢學習。

2,實例化委托

使用 Delegate.CreateDelegate() 方法實例化一個委托,使用 Delegate.DynamicInvoke() 調用委托並且傳遞參數。

使用形式

CreateDelegate(Type, Object, MethodInfo)

Type 是此委托類型,Object 、MethodInfo 是實例類型、方法。

有兩種情況,一種是實例方法、一種是靜態方法。

我們創建一個委托以及類型

    delegate int Test(int a, int b);
    public class MyClass
    {
        public  int A(int a, int b)
        {
            Console.WriteLine("A");
            return a + b;
        }
        public static int B(int a, int b)
        {
            Console.WriteLine("B");
            return a - b;
        }
    }

Main() 中 實驗代碼如下

            // 綁定實例方法
            Delegate d1 = Delegate.CreateDelegate(typeof(Test), new MyClass(), "A");

            // 綁定靜態方法
            Delegate d2 = Delegate.CreateDelegate(typeof(Test), typeof(MyClass), "B");

            Console.WriteLine(d1.DynamicInvoke(333,333));
            Console.WriteLine(d2.DynamicInvoke(999,333));

            Console.ReadKey();

輸出

A
666
B
666

3,實例化泛型類型

3.1 實例化泛型

實例化一個泛型類型時,可以按照實例化普通類型過程操作

            // 正常
            Type type = typeof(List<int>);
            object obj = Activator.CreateInstance(type);

            // 下面的會報錯
            Type _type = typeof(List<>);
            object _obj = Activator.CreateInstance(_type);

使用 Activator.CreateInstance 方法實例化一個泛型類型時,必須是 已綁定類型參數 的泛型 Type。

List<int> 已綁定 √; List<> 未綁定 ×。

另外,通過 ConstructorInfo.Invoke() 實例化也是一樣的。

    public class MyClass<T>
    {
        public MyClass(T a)
        {
            Console.WriteLine(a);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // 正常
            ConstructorInfo type = typeof(MyClass<int>).GetConstructor(new Type[] { typeof(int) });
            object obj = type.Invoke(new object[] { 666 });

            Console.ReadKey();
        }
    }

3.2 構造封閉泛型類型以及反轉

3.2.1 構造封閉構造函數

有時候,傳遞過來的恰恰是 List<> 呢?

使用 Type.MakeGenericType(Type)

我們可以這樣多一步,將未綁定類型參數的泛型 Type,轉為封閉的 泛型 Type。

            Type type = typeof(List<>);

            // 構建泛型 Type
            Type _type = type.MakeGenericType(typeof(int));
            object _obj = Activator.CreateInstance(_type);

3.2.2 去除泛型類型的參數類型綁定

使用 Type.GetGenericTypeDefinition() 方法可以去除一個已綁定參數類型的泛型類型的參數類型。

            Type type = typeof(List<int>);
            Console.WriteLine(type.FullName);
            // 構建泛型 Type
            Type _type = type.GetGenericTypeDefinition();
            Console.WriteLine(_type.FullName);

輸出

System.Collections.Generic.List`1[[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
System.Collections.Generic.List`1

List<int> 變成了 List<>

3.2.3 實踐一下

上面介紹了泛型類型的實例化和兩個關於參數類型的使用,下面來實踐一下

        static void Main(string[] args)
        {
            Type typeA = typeof(Console);
            Type typeB = typeof(List<>);
            Type typeC = typeof(List<int>);

            去除泛型類型綁定的參數類型(typeA);
            去除泛型類型綁定的參數類型(typeB);
            去除泛型類型綁定的參數類型(typeC);
            Console.ReadKey();
        }

        /// <summary>
        ///  將 List<T> 轉為 List<>
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static (bool, Type) 去除泛型類型綁定的參數類型(Type type)
        {
            // 檢查是否泛型類型
            if (type.IsGenericType == false)
            {
                Console.WriteLine("此類型不是泛型類型");
                return (false, type);
            }

            // 檢查是否是未綁定參數類型的泛型類型
            if (type.IsGenericTypeDefinition)
            {
                Console.WriteLine("本來就不需要處理");
                return (true, type);
            }

            Type _type = type.GetGenericTypeDefinition();
            Console.WriteLine("處理完畢");
            return (true, _type);
        }
    }

此文僅授權《NCC 開源社區》訂閱號發布


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM