一、元数据和反射
1.1 定义
大多数程序都要处理数据,包括读、写、操作和显示数据。然而,对于某些程序来说,它们操作的不是数字、文本或图形,而是程序和程序类型本身的信息。
● 有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中
● 程序在运行时,可以查看其他程序集或其本身的元数据。一个运行的程序查看本身的元数据和其他程序的元数据的行为叫做反射(reflection)
反射提供了封装程序集、模块和类型的对象(Type类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了特性,可以利用反射对它们进行访问。【MSDN】
1.2 使用场景
● 需要访问程序元数据的特性
● 检查和实例化程序集中的类型
● 在运行时构建新类型。使用System.Reflection.Emit中的类
● 执行后期绑定,访问在运行时创建的类型的方法。【MSDN】
1.3 形象说明
地球的内部结构:地球的内部结构大体可以分为三层:地壳、地幔和地核。地壳是固体,地核是液体,地幔则是半液半固的结构(地理知识)。如何在地球表面不用深入地球内部就知道其内部结构呢?答案是:向地球发射“地震
波”。地震波分两种,一种是横波,另一种是纵波。横波只能穿透固体,而纵波既可以穿透固体又可以穿透液体。通过在地面对纵波和横波的返回情况,我们就可以大体断定地球内部的构造了。
B型超声波:大家体检的时候都做过B超吧,B超可以透过肚皮探测到你的内脏的生理情况。这是如何做到的呢?答案是:它可以透过肚皮向你体内发射B型超声波,当超声波遇到内脏壁的时候就会产生一定的“回音”反射,然后
把“回音”进行处理就可以显示出你的内脏的情况了。(部分细节不予追究)
大家注意到这两个例子的共同特点,就是从一个对象的外部去了解对象内部的构造,而且都是利用了波的反射功能。在.NET中的反射也可以实现从对象的外部来了解对象(或程序集)内部结构的功能,哪怕你不知道这个对象(或
程序集)是什么,另外.NET中的反射还可以动态创建出对象并执行它其中的方法。
反射是.NET中重要的机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。有了反射,即可对每一个类型了如指掌。另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道。
1.4 反射的主要用途
-
使用 Assembly 定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例。
-
使用 Module 发现以下信息:包含模块的程序集以及模块中的类等。 您还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
-
使用 ConstructorInfo 发现以下信息:构造函数的名称、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。 使用 Type 的 GetConstructors 或 GetConstructor 方法来调用特定的构造函数。
-
使用 MethodInfo 发现以下信息:方法的名称、返回类型、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。 使用 Type 的 GetMethods 或 GetMethod 方法来调用特定的方法。
-
使用 FieldInfo 发现以下信息:字段的名称、访问修饰符(如 public 或 private)和实现详细信息(如 static)等;并获取或设置字段值。
-
使用 EventInfo 发现以下信息:事件的名称、事件处理程序数据类型、自定义特性、声明类型和反射类型等;并添加或移除事件处理程序。
-
使用 PropertyInfo 发现以下信息:属性的名称、数据类型、声明类型、反射类型和只读或可写状态等;并获取或设置属性值。
-
使用 ParameterInfo 发现以下信息:参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。
-
当您在一个应用程序域的仅反射上下文中工作时,请使用 CustomAttributeData 来发现有关自定义特性的信息。 通过使用 CustomAttributeData,您不必创建特性的实例就可以检查它们。【MSDN】
注:要使用反射,必须使用System.Reflection命名空间
二、Type类
2.1 简介
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性,使用这个类的对象能让我们获取程序使用的类型的信息。
由于Type是抽象类,因此它不能有实例,而是在运行时CLR创建从Type(RuntimeType)派生的类的实例。Type包含了类型信息,当我们要访问这些实例时,CLR不会返回派生类的引用而是返回Type基类的引用。为了简单起见,下面篇幅中我会把引用所指向的对象称为Type类型的对象。
Type类的重要事项:
● 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
● 程序中用到的每一个类型都会关联到独立的Type类型的对象
● 不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
如果你对C#中有哪些类型不是很了解,那么知识扩展: C#中的类型有:预定义的类型(int,long和string等),BCL中的类型(Console,IEnumerable等)以及用户自定义的类型(MyClass,MyDel等)
2.2 Type类的部分常见成员
成员 | 成员类型 | 描述 |
Name | 属性 | 返回类型的名字 |
FullName | 属性 | 返回数据类型的完全限定名(包括命名空间名) |
NameSpace | 属性 | 返回包含数据类型声明的命名空间 |
Assembly | 属性 | 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集 |
GetConstructor(), GetConstructors() | 方法 | 返回ConstructorInfo类型,用于取得该类的构造函数的信息 |
GetEvent(), GetEvents() | 方法 | 返回EventInfo类型,用于取得该类的事件的信息 |
GetField(), GetFields() | 方法 | 返回FieldInfo类型,用于取得该类的字段(成员变量)的信息 |
GetInterface(), GetInterfaces() | 方法 | 返回InterfaceInfo类型,用于取得该类实现的接口的信息 |
GetMember(), GetMembers() | 方法 | 返回MemberInfo类型,用于取得该类的所有成员的信息 |
GetMethod(), GetMethods() | 方法 | 返回MethodInfo类型,用于取得该类的方法的信息 |
GetProperty(), GetProperties() | 方法 | 返回PropertyInfo类型,用于取得该类的属性的信息 |
有关Type类的详细信息请参见【MSDN】
2.3 获取Type对象
我们可以使用GetType方法和typeof运算符来获取Type对象。object类型包含了一个叫做GetType的方法,它返回对实例的Type对象的引用。由于每一个类型最终都是从object继承的,所以我们可以在任何类型对象上使用GetType方法来获取它的Type对象
a. GetType()用法示例
string str = "fun"; Type t = str.GetType();
还有一种静态方法:public static Type GetType (string typeName);
Type myType1 = Type.GetType("System.Int32");
备注:如果typeName
找不到,在调用GetType(String)方法将返回null
。 它不会引发异常。
b. typeof用法
Type t1 = typeof(string);
2.4 示例
using System; using System.Reflection; namespace Type类使用 { class Base { public int BaseField = 0; } class Derived : Base { public int DerivedField = 0; public int change { set; get; } } class Program { static void Main(string[] args) { Base b = new Base(); Derived d = new Derived(); Base[] bd = new Base[] { b, d }; foreach(var val in bd) { Type t = val.GetType(); //获取类型 Console.WriteLine("object type:{0}", t.Name); FieldInfo[] fi = t.GetFields(); //获取字段信息 foreach(var f in fi) { Console.WriteLine(" Field: {0}", f.Name); } } Console.ReadKey(); } } }
上面的例子,是对类中public修饰的成员的操作,实际上private和protected修饰的成员也可以访问到,详情请看:C#之玩转反射
【备注:本文参考了 C#反射详解 ,如有冒犯请联系删除】