反射初探
《.NET高级编程》中对反射的定义是:通过编程访问程序集中元数据的技术称为反射。
元数据
那什么是元数据呢,这里引用CSDN某帖子上对MSDN上对元数据定义的翻译(也有可能是直接从中文版的MSDN Copy过来的)。
“
元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (PE) 文件或存储在内存中的程序进行描述。将您的代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而将代码转换为 Microsoft 中间语言 (MSIL) 并将其插入到该文件的另一部分中。在模块或程序集中定义和引用的每个类型和成员都将在元数据中进行说明。当执行代码时,运行库将元数据加载到内存中,并引用它来发现有关代码的类、成员、继承等信息。
元数据以非特定语言的方式描述在代码中定义的每一类型和成员。元数据存储以下信息:
程序集的说明:
标识(名称、版本、区域性、公钥)
导出的类型
该程序集所依赖的其他程序集
运行所需的安全权限
类型的说明:
名称、可见性、基类和实现的接口。
成员(方法、字段、属性、事件、嵌套的类型)。
特性(Attribue)
修饰类型和成员的其他说明性元素。
元数据的优点
对于一种更简单的编程模型来说,元数据是关键,该模型不再需要接口定义语言 (IDL) 文件、头文件或任何外部组件引用方法。元数据允许 .NET 语言自动以非特定语言的方式对其自身进行描述,而这是开发人员和用户都无法看见的。另外,通过使用特性,可以对元数据进行扩展。元数据具有以下主要优点:
自描述文件。
公共语言运行库模块和程序集是自描述的。模块的元数据包含与另一个模块进行交互所需的全部信息。元数据自动提供 COM 中 IDL 的功能,允许将一个文件同时用于定义和实现。运行库模块和程序集甚至不需要向操作系统注册。结果,运行库使用的说明始终反映编译文件中的实际代码,从而提高应用程序的可靠性。
”
本人理解为在生成的程序集文件中,有一部分是用来保存对程序集中提供的功能(所提供的类型列表,每个类型的具体定义信息--构造函数,方法,属性,字段等等)和该程序集所依赖的一些信息的说明(该程序集所引用的其他程序集等)。这一部分信息即称为程序集的元数据。VS中的Object Browser应该就是从程序集的元数据中获取信息。
例如,创建一个叫TestLib的工程,然后再定义一个Lib类:
{
public class Lib
{
public string A { get; set; }
public void MetodA(string a, int b)
{
Console.WriteLine("Lib.MethodA()");
}
}
}
编绎后会生成一个dll,如果我们另一个工程中引用该dll,然后在VS中用Object Browser查看该Dll的信息如下:
这里列出了TestLib这个程序集的命名空间列表,类型列表。类型的方法,属性列表。这些数据就是从程序集的元数据中获取。
反射
正如《.NET高级编程》的定义,反射其实就是通过代码的形式动态的对程序集的元数据进行操作的技术。通过反射,我们能动态的获取某个程序集中的类型,某个特定类型的方法,属性定义列表等。并能通过反射技术动态的获取/设置某个特定类型实例的属性值,以及动态调用其方法。(若我们调用的是类型的构造函数,那我们就是在利用反射动态的创建类型的实例了)。.NET Framework反射技术中比较重要的两个类我认为是Assembly和Type.
Assembly用来描述一个程序集,而Type用来描述一个类型。上述的反射操作都是围绕这两个类型进行。下面来举例说明反射的功能:
1. 获取程序集中的导出类型并动态创建其中的一个类型的实例。
其于上面所建立的Lib类,编写如一代码:
{
Assembly assembly = Assembly.Load(new AssemblyName("TestLib"));
Type[] types = assembly.GetExportedTypes();
if (types.Length > 0)
{
foreach (Type type in types)
{
Console.WriteLine(type.FullName);
}
object dynamicObj = assembly.CreateInstance(types[0].FullName);
}
Console.ReadKey();
}
这段代码会获取到TestLib的所有导出类,然后用为其中一个类型创建了一个实例。
2. 获取特定类型的某个方法元数据,并动态调用该类型某个实例的该方法:
基于1中的代码添加如下代码:
MethodInfo methodInfo = libType.GetMethod("MetodA");
methodInfo.Invoke(dynamicObj, new object[] { "abc", 1 });
这段代码先获取类型中所定义的MethodA这个方法,然后动态的去调用之前动态创建出来的类型Lib的实例的MethodA方法。
3. 获取构造函数,并通过构数来动态构造类型的实例
object dynamicObjViaCon = constructorInfo.Invoke(new object[] { });
代码中的Type数组是用来表示希望获取的构造函数的参数类型列表,而object数组是表示调用构造函数所传入的参数列表。
4. 获取属性元数据和特定实例的中该属性的值。
object value = propertyInfo.GetValue(dynamicObjViaCon, null);
propertyInfo.SetValue(dynamicObjViaCon, "A", null);
value = propertyInfo.GetValue(dynamicObjViaCon, null);
通过类似的方法可以操作Field,Event等其他类型成员的信息。
特性(Attribute)
Attribute是一种拓展元数据的途径。我们可以将Attribute标识在某个类型成员的定义上,那么这个成员则会多出一个该Attribute所定义的描述信息。在运行时可以通过反射动态的去拿到这个成员的Attribute,然后根据具体的Attribute进行不同的处理。
下面举例说明:
首先自定义一个Attribute
{
public CustomAttribute()
: base()
{
}
}
然后修改Lib类的定义,在MethodA的定义中加上CustomAttribute的标签
public void MetodA(string a, int b)
{
Console.WriteLine("Lib.MethodA()");
}
这样再在之前的例子(2. 获取特定类型的某个方法元数据)基础上添加如下代码:
object[] attrs = methodInfo.GetCustomAttributes(false);
这样即可将MethodA上添加的Attributes获取到,接下来我们便可以根据具体的Attribute定义具体的业务逻辑。
{
// Put special logic here.
}