类库探源-反射机制
导读
1、什么是反射
2、反射的基石——元数据
3、用ildasm.exe 查看元数据
4、System.Reflection 命名空间下需关注的成员
5、获取 Type 实例的方式
6、晚绑定与System.Activator 类
什么是反射
在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。这是Wiki的解释。C#和Java都支持反射,主流的C#和Java框架中都大量应用了反射。反射的主要应用场景如下:
1、使用反射来动态分析来探索程序集中的类型(ildasm.exe 和 ILSpy 等反编译工具)
2、晚绑定
3、Attribute
4、访问某个类的私有成员
5、CLR在执行某些操作是会用到反射机制如:序列化对象时会用到反射、GC用反射构造引用树、指类型的Equal()方法使用反射逐一比较相应字段等
反射的基石——元数据
上一节说明了反射的定义以及反射的主要应用场景。那么为什么C#和Java会有反射机制?或者说是不是所有编程语言都有反射机制?要回答这些问题,我们就必须谈到反射的基石——元数据。
什么是元数据?
元数据是描述数据的数据(MetaDat is data about data)。这个定义看着可能比较模糊,以C#为例来说,元数据就是记录类、结构以及成员变量的一个数据结构。
用ildasm.exe 查看元数据
上一节对于元数据的定义可能比较模糊,这一节我将一个例子结合 ildasm.exe 工具谈下元数据
using System; class DemoLib { private string _name; public string Name { get{return _name;} set{_name = value;} } public void Print() { Console.WriteLine(Name); } }
csc /t:library DemoLib.cs
用 Ildasm.exe 打开刚才生成的 DemoLib.dll ,按住 Ctrl +M 得到当前程序集的元数据信息如图所示
我们细看下这个 MetaInfo
TypeDef #n、TypeRef #n、Assembly 、AssemblyRef #n、User Strings
上面的5个地方是我们需要重点看的
TypeDef #n 代表类型定义 n是正整数(下同)
TypeRef #n 代表类型引用(当前类型引用的类型)
Assembly 当前程序集的信息
AssemblyRef #n 当前程序集引用的程序集信息
User Strings 当前程序集用到的字面字符串值
我们主要看下 TypeDef 下的内容
Field #1 (04000001) 类型中定义的字段
Method #1 (06000001) 类型中定义的方法
Property #1 (17000001) 类型中定义的属性
元数据中记录类型数据,这也就是为什么元数据被称为描述数据的数据。
System.Reflection 命名空间下需关注的成员
System.Reflection 命名控件提供了与反射机制相关的成员
类型 | 作用 |
Assembly | 该抽象类包含了许多静态方法,通过它可以加载、了解和操纵一个程序集 |
MemberInfo | 该类是抽象基类,它为EventInfo、FieldInfo、MethodInfo和PropertyInfo类型定义了公共的行为 |
MethodInfo | 该抽象类包含给定方法的信息 |
下图简要说明了反射的一些过程
获取 Type 实例的方式
由上图可知,获取一个对象的Type实例是反射机制的核心操作。在C#中一共提供了4中获取Type实例的方法,现归纳如下
1、使用 System.Object.GetType() 获得 Type实例
显而易见,想要使用这个方法,必须得到类型的编译时信息,并且在内存中有类型实例
2、使用 typeof() 得获得Type 实例
需要提供类型的编译时信息, typeof 需要的是类型的强类型名称,而不是文本信息
3、使用 System.Type.GetType() 获得 Type实例
通过调用 System.Type 类的静态方法 GetType() 指定类型的完全限定名,获得Type实例,采用这种方法,我们不需要编译时指定类型信息,System.Type.GetType() 方法有多个重载方法,详细信息查看MSDN
4、使用 Assmbly.GetType( ) 获得Type实例
第四种是最常用的,先加载dll获得程序集实例,在通过程序集实例获得对应类型信息
晚绑定与System.Activator 类
晚绑定是一种创建一个给定类型的实例并在运行时调用其成员,而不需要在编译时知道它存在的一种技术。通过这种方法使我们的系统扩展性加强
System.Activator 类提供了 创建实例的方法 CreateInstance() 这个方法有很多重载方法详见MSDN
我们来看一个反射实例
using System; namespace XXX.Common.Lib { public class TestLib { public TestLib() { Console.WriteLine("TestLib Ctor."); } public int Sum(int a,int b) { Console.WriteLine("Method TestLib.Sum() Invoked"); return a+b; } } }
csc /t:library Lib.cs
得到 Lib.dll
我们在写一个 App.cs 通过反射的方式加载 Lib.cs 中的Sum 方法
using System; using System.Reflection; class App { static void Main() { Assembly assmbly = Assembly.LoadFrom("Lib.dll"); // LoadFrom("FileName.dll") Load(FileName) Type type = assmbly.GetType("XXX.Common.Lib.TestLib"); object obj = Activator.CreateInstance(type); // 进入 XXX.Common.Lib.TestLib 构造器 MethodInfo mi = type.GetMethod("Sum"); object[] pars = new object[]{1,2}; int result = 0; result = (int)mi.Invoke(obj,pars); Console.WriteLine(result); } }
csc app.cs
运行结果
常见的3种加载程序集的方法:
1、Assembly.Load
使用 Assembly.Load 加载程序集的顺序如下:首先会找GAC(全局程序集缓存),然后到应用程序的根目录查找,最后到应用程序的私有路径查找
2、Assembly.LoadFile
该方法从指定路径的文件来加载程序集
3、Assembly.LoadFrom
该方法从指定的路径来加载程序集,实际上该方法被调用的时候,CLR会打开这个文件,获取其中程序集版本、公钥等信息,然后把它们传递给Load方法,通过Load方法来加载程序集。
本文完