C#中的反射

1.准备工作

在接触反射之前,我们首先需要了解一下什么是元数据(Metadata)。元数据,就是描述数据的数据,比如数据集的名称、关系、字段、约束等。我们编写的C#代码经过编译器编译后,会生成相应的中间语言(IL),同时也会生成一套用于描述它的清单,即元数据。

2.什么是反射?

反射是指对程序集中的元数据进行检查的过程。 那么利用反射可以做什么事情呢?

  • 它允许在运行时查看特性(attribute)信息。
  • 它允许审查集合中的各种类型,以及实例化这些类型。
  • 它允许延迟绑定的方法和属性(property)。
  • 它允许在运行时创建新类型,然后使用这些类型执行一些任务。

3.如何使用反射?

我们可以通过System.Type的实例来访问类型的元数据,该对象包含了对类型实例的成员进行枚举的方法。下面是System.Type提供的部分成员。

Type type = typeof(string);  
// 类型名称  
string typeName = type.Name;  
// 类型是否public  
bool typeIsPublic = type.IsPublic;  
// 类型的基类  
Type? baseType = type.BaseType;  
// 类型实现的接口  
Type[] interfaces = type.GetInterfaces();  
// 类型在哪个程序集定义  
Assembly typeAssembly = type.Assembly;  
// 类型的属性  
PropertyInfo[] properties = type.GetProperties();  
// 类型的方法  
MethodInfo[] methods = type.GetMethods();  
// 类型的字段  
FieldInfo[] fields = type.GetFields();  
// 类型的特性  
IEnumerable<Attribute> attributes = type.GetCustomAttributes();

从上面的实例可以看出,我们要使用反射首先要获取System.Type的实例。获取Type的实例有如下两种方式:

  • GetType()适用于可以实例化的类型,需要通过类型的实例调用。
DateTime dateTime = new DateTime();
Type type = dateTime.GetType();
  • typeof()可以通过类型直接获取。
Type type = typeof(string);

获取到Type对象后,就可以访问到目标类型的元数据。例如我们可以获取到类型的构造方法,然后通过该构造方法创建一个实例:

class Student
{
	public string Name { get; set; }
	public int Age { get; set; }

	public Student()
	{
		Name = "AAA";
		Age = 12;
	}
	public Student(string name)
	{
		Name = name;
	}

	private Student(string name, int age)
	{
		Name = name;
		Age = age;
	}

	public void PrintStudent()
	{
		Console.WriteLine($"姓名:{Name} 年龄:{Age}");
	}
}
public static void ReflectPracticeMain()
{
	Type stuType = typeof(Student);

	var constructors = stuType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
	var constructor = Array.Find(constructors, c => c.GetParameters().Length == 2);
	if (constructor != null)
	{
		Student student =(Student) constructor.Invoke(new object[]{"李四",12});
		student.PrintStudent();
	}
}

其输出结果为:

在泛型类型中使用反射

反射同样可以运用在泛型类型中。例如用来判断类型参数的类型:

class MyList<T>
{
	public void Add(T item)
	{
		var type = typeof(T);
		if (type == typeof(int))
		{
			// ...
		}
	}
}

通过Type类型中的ContainsGenericParameters属性判断类或方法是否包含尚未设置的泛型参数;IsGenericType属性可以判断类型是否是泛型:

var type1 = typeof(MyList<>);
Console.WriteLine(type1.ContainsGenericParameters);
Console.WriteLine(type1.IsGenericType);

var type2 = typeof(MyList<int>);
Console.WriteLine(type2.ContainsGenericParameters);
Console.WriteLine(type2.IsGenericType);

其输出结果如下:

通过GetGenericArguments()方法可以获取泛型实参的列表:

var type2 = typeof(Action<int,float,string>);
Type[] genericArguments = type2.GetGenericArguments();

foreach (var argument in genericArguments)
{
	Console.WriteLine($"param:{argument.FullName}");
}

其输出结果如下:

4.使用dynamic调用反射

反射的关键功能之一是动态查找和调用特定类型的成员,这要求在执行时识别成员名或其他特征。dynamic动态对象提供了更简单的办法来通过反射调用成员。但限制是编译时需要知道成员名和签名

下面来看一段示例代码:

dynamic data = "这是一段字符串";
Console.WriteLine(data);
data = (double)data.Length;
Console.WriteLine($"长度:{data}");

输出如下

相较于前面所说的反射,这种将对象声明为dynamic类型的方式并没有查找特定的MethodInfo实例进行调用。相反,在dynamic类型实例上调用方法,编译器并不会检查该成员是否可用。但这并不意味着类型安全被完全放弃,类型检查器会在执行时为dynamic类型调用。如果执行时发现实际并没有这个成员,则会抛出RuntimeBinderException。

dynamic的特征

  • dynamic是通知编译器生成代码的指令。当运行时遇到第一个dynamic调用时,可以将请求编译成CIL,再调用新编译的调用。将类型指定成dynamic,相当于从概念上包装了原始类型。这样便不会发生编译时验证。此外在运行时调用一个成员时,包装器会解释调用,并相应地调度(或拒绝)它。
  • 任何能转换成object的类型都能转换成dynamic
  • 从dynamic到一个替代类型的成功转换需要依赖基础类型的支持
  • dynamic的基础类型在每次赋值时都可能改变。这一点和var不同,var不能重新赋值成一个不同的类型。
  • 验证基础类型上是否存在指定签名要推迟到运行时进行
  • 任何dynamic成员调用都将返回dynamic对象。即便为data调用ToString()方法,也不会返回一个string对象。
  • 用dynamic实现的反射不支持扩展方法。这一点和使用Type实现的反射一样,只有实现类型才能调用扩展方法。

参考文献:
[1]马克·米凯利斯.C#8.0本质论[M].机械工业出版社.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值