type
status
date
summary
tags
category
icon
一、概述
设想有这么一种情况,一家公司发布了宿主应用程序后,交给其他公司创建加载项(add-in)来扩展宿主应用程序。那其他公司在对程序类型一无所知的情况下,如何在运行时发现类型的信息、创建类型的实例以及访问类型的成员呢?答案是我们可以通过反射来实现。
流程概述
二、流程详情
加载程序集
我们知道,JIT编译器将方法的IL代码编译成本机代码时,会查看代码中引用了哪些类型,然后在运行时,JIT编译器利用程序集的TypeRef和AssemblyRef元数据表来确定哪一个程序集定义了所引用的类型。
Assembly程序集名称
在AssemblyRef元数据表的记录项中,包含了构成程序集强名称的各个部分——包括名称(无扩展名和路径)、版本、语言文化和公钥标记——并把他们连接成一个字符串。然后JIT编译器尝试将与该标识匹配的程序集加载到AppDomain中,例如一个程序集的名称可以为:
string name = ”System.Data, version=4.0.0.0, culture=neutral, PublicKeyToken=b77a5c561934e089”
CLR在内部使用System.Reflection.Assembly类的静态
Load方法将程序集显式的加载到AppDomain中。如果Load找到指定的程序集,会返回程序集的Assembly对象的引用。如果
Load没有找到指定程序集,会抛出一个System.IO.FileNotFoundException异常。在大多数可扩展的应用程序中,Assembly的Load方法是将程序集加载到AppDomain的首选方式。LoadFrom方式加载
对于制定了路径名的程序集,我们能通过调用Assembly的
LoadFrom方法调用(本质上内部还是通过Load查找)在内部,
LoadFrom首先调用System.Reflection.AssemblyName类的静态GetAssemblyName方法。该方法打开指定路径下的文件,找到AssemblyRef元数据表的记录项,提取程序集标识信息(版本、公钥标识等),然后以System.Reflection.AssemblyName的形式返回这些信息。随后,LoadFrom方法在内部调用Load方法,将AssemblyName对象传给它。最后,CLR通过重定向策略,在各个位置查找匹配的程序集。Load找到匹配的程序集时会加载它,并返回代表已加载程序集的Assembly对象,LoadFom方法将会返回这个对象;如果Load没有找到匹配的程序集,LoadFrom会加载通过实参传递的路径中的程序集。重要提示
一台机器可能同时存在具有相同标识的多个程序集,所以强烈建议每次生成程序集时都更改版本号,确保每个版本都有自己的唯一性标识,确保LoadFrom方法的行为符合预期
获取程序集中包含的类型
通过反射,我们能够获取程序集中定义了哪些类型,最常用的API是Assembly的ExportedTypes属性。下面例子展示加载一个程序集,并显示其中定义的所有公开导出类型(即定义为Public的类型,它们在程序集外部可见)
上述代码中,System.Type对象代表一个类型的引用,而不是类型定义。众所周知,System.Object在C#中是所有对象的基类,它定义了公共的非虚实例方法
GetType。调用这个方法时,CLR会判断指定对象的类型,并返回对该类型的Type对象的引用。由于在一个AppDomain中,每个类型只有一个Type对象,所以可以使用相等和不等操作符判断两个对象是不是相同的类型:构建类型的实例
在上面的步骤中,我们首先加载了目标程序集,然后通过Type方法获取到了Type派生对象的引用,如今就可以构造该类型的实例了。FCL提供了以下几个方法
System.Activator的CreateInstance方法
Activator类提供了静态
CreateInstance方法的几个重载版本。调用方法时既可传递一个Type对象引用,也可传递标识了类型的String,该方法执行成功后会返回对新对象的引用。System.Activator的CreateInstanceFrom方法
Activator类还提供了一组静态
CreateInstanceFrom方法。它们与CreateInstance的行为相似,只是必须通过字符串参数来指定类型及其程序集。该方法返回的是一个ObjectHandle对象的引用,必须调用ObjectHandle的Unwrap方法进行具体化。System.AppDomain的方法
AppDomain类型提供了4个用于构造类型实例的方法,分别是
CreateInstance,CreateInstanceAndUnwrap,CreateInstanceFrom和CreateInstanceFromAndUnwrap。这些方法的行为和Activator类的方法相似,区别在于他们都是实例化方法,允许指定在哪个AppDomain中构造对象。System.Reflection.ConstructorInfo的Invoke实例方法
使用一个Type对象引用,可以绑定到一个特定的构造器,并获取对构造器的ConstructorInfo对象的引用。然后,可利用ConstructorInfo对象引用来调用它的
Invoke方法。类型总是在调用AppDomain中创建,返回的是对新对象的引用利用这些方法,可为除数组(System.Array派生类型)和委托(System.MulticastDelegate派生类型)之外的所有类型创建对象。如果想要创建数组类型的对象,需要调用Array的静态
CreateInstance方法。创建委托则要调用MethodInfo的静态CreateDelegate方法。访问和修改
读写权限
exe和dll区别?
三、实例分析
三种方法
常规
推荐
推荐
四、反射的性能
反射是相当强大的机制,它能够允许开发人员在运行时发现并使用编译时还不了解的类型及其成员。但是,它有以下两个缺点:
- 反射造成编译时无法保证类型的安全性。
五、总结
事实上,由于反射并不具备良好的性能和编译时不能保证类型的安全性,我们只有在极少数的场景下才需要使用反射实现功能。下面是常见的两种场景
- 如果类库需要理解类型的定义才能提供丰富的功能,就适合使用反射。例如Microsoft Visual Studio设计器在Web窗体或Windows窗体上放置控件时,能利用反射向开发人员展示需要显示的属性
- 在运行时,当应用程序需要从特定程序集中加载特定类型以执行特定任务时,也要使用反射
- 作者:Felix
- 链接:felix1125.com/article/217fa647-c702-4ce0-9237-703d8d3c05be
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。