C#反射与特性(四 实例化类型

简介: C#反射与特性(四 实例化类型

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 。

那么,问题来了

微信图片_20220502163432.jpg


反射后,少不得一顿装箱拆箱了。

目前来说,我们使用不了 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 异常。


微信图片_20220502163438.png


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 开源社区》订阅号发布

相关文章
|
1月前
|
编译器 C# 开发者
C# 9.0 新特性解析
C# 9.0 是微软在2020年11月随.NET 5.0发布的重大更新,带来了一系列新特性和改进,如记录类型、初始化器增强、顶级语句、模式匹配增强、目标类型的新表达式、属性模式和空值处理操作符等,旨在提升开发效率和代码可读性。本文将详细介绍这些新特性,并提供代码示例和常见问题解答。
45 7
C# 9.0 新特性解析
|
1月前
|
C# 开发者
C# 10.0 新特性解析
C# 10.0 在性能、可读性和开发效率方面进行了多项增强。本文介绍了文件范围的命名空间、记录结构体、只读结构体、局部函数的递归优化、改进的模式匹配和 lambda 表达式等新特性,并通过代码示例帮助理解这些特性。
36 2
|
3月前
|
编译器 C# Android开发
震惊!Uno Platform 与 C# 最新特性的完美融合,你不可不知的跨平台开发秘籍!
Uno Platform 是一个强大的跨平台应用开发框架,支持 Windows、macOS、iOS、Android 和 WebAssembly,采用 C# 和 XAML 进行编程。C# 作为其核心语言,持续推出新特性,如可空引用类型、异步流、记录类型和顶级语句等,极大地提升了开发效率。要在 Uno Platform 中使用最新 C# 特性,需确保开发环境支持相应版本,并正确配置编译器选项。通过示例展示了如何在 Uno Platform 中应用可空引用类型、异步流、记录类型及顶级语句等功能,帮助开发者更好地构建高效、优质的跨平台应用。
236 59
|
1月前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
35 3
|
1月前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
57 1
|
2月前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
42 1
|
1月前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
16 0
|
2月前
|
C#
C# 可空类型(Nullable)
C# 单问号 ? 与 双问号 ??
52 12
|
4月前
|
程序员 C#
C# 语言类型全解
C# 语言类型全解
27 0
|
4月前
|
C# 索引
C#各大版本特性
C#各大版本特性
78 0