1. 反射的定义
定义:审查元数据并收集关于它的类型信息的能力。元数据(编译以后的最基本数据单元)就是一大堆的表,当编译程序集或者模块时,编译器会创建一个类定义表,一个字段定义表,和一个方法定义表等,。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
2. 反射的特点
2.1 反射通常具有以下用途。
(1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
(2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。
(4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法来调用特定的方法。
(5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
(6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
(7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
(8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
2.2 反射的作用:
1. 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现 有对象中获取类型
2. 应用程序需要在运行时从某个特定的程序集中载入一个特定的类型,以便实现某个任务时可以用到反射。
3. 反射主要应用与类库,这些类库需要知道一个类型的定义,以便提供更多的功能。
应用要点:
1. 现实应用程序中很少有应用程序需要使用反射类型
2. 使用反射动态绑定需要牺牲性能
3. 有些元数据信息是不能通过反射获取的
4. 某些反射类型是专门为那些clr 开发编译器的开发使用的,所以你要意识到不是所有的反射类型都是适合每个人的。
2.3 反射类所使用的设计模式(注:信息从MSDN获取)
System.Reflection 命名空间中最常用的方法都使用统一的模式。Module、Type 和 MemberInfo 类的成员使用下表中所示的设计模式。
成员签名 |
说明 |
MyInstance[] FindXxx(filter,filterCriteria) |
查找并返回经过筛选的类型列表,或者在当前类型没有实现任何匹配筛选器的类型的情况下返回空数组。 示例:System.Type.FindInterfaces(System.Reflection.TypeFilter,System.Object) |
MyInstance GetXxx(<parameters>) |
返回由 <parameters> 唯一指定的类型。如果不存在这样的类型,成员将返回空引用(在 Visual Basic 中为 Nothing)。请注意,<parameters> 唯一地指定一个实例。 |
MyInstance[] GetXxxs() |
返回所有公共类型。如果不存在公共类型,成员将返回空数组。 |
MyInstance[] GetXxxs(<parameters>) |
返回由 <parameters> 指定的所有类型。如果不存在这样的类型,成员将返回空数组。请注意,<parameters> 并不一定指定唯一的实例。 |
另一个常用的设计模式是使用委托。它们通常在反射中用来支持对返回对象数组的方法的结果集进行筛选。
3. 实现反射
using System.Reflection; //获取程序集的Assembly 对象和模块时所必需的语法 Assembly a = typeof(Object).Module.Assembly; 3.1 反射AppDomain中包含的所有程序集 //通过GetAssemblies 调用appDomain的所有程序集 foreach (Assembly assem in Appdomain.currentDomain.GetAssemblies()) { //反射当前程序集的信息 reflector.ReflectOnAssembly(assem); } 3.2查看类型信息 //从已加载的程序集中获取Type 对象 Assembly a = Assembly.LoadFrom("05WinFormTest.exe"); Type[] types = a.GetTypes(); foreach (Type t in types) { Console.WriteLine(t.FullName); } //下面的示例显示如何列出一个类(此示例中为 System.String 类)的构造函数 using System; using System.Reflection; class ListMembers { public static void Main(String[] args) { Type t = typeof(System.String); Console.WriteLine("Listing all the public constructors of the {0} type", t); // Constructors. ConstructorInfo[] ci = t.GetConstructors(BindingFlags.Public | BindingFlags.Instance); Console.WriteLine("//Constructors"); PrintMembers(ci); } public static void PrintMembers(MemberInfo[] ms) { foreach (MemberInfo m in ms) { Console.WriteLine("{0}{1}", " ", m); } Console.WriteLine(); } } //其他方法也是一样 //获取类型的字段信息 FieldInfo [] myfields=type.GetFiedls(); //获取方法信息 MethodInfo myMethodInfo=type.GetMethods(); //获取属性信息 PropertyInfo [] myproperties=type.GetProperties(); //获取事件信息 EventInfo[] Myevents = type.GetEvents();
具体的可察看MSDN http://msdn2.microsoft.com/zh-cn/library/t0cs7xez(VS.80).aspx
3.3 动态加载和使用类型
篇幅有限,这里不多讲,具体参照http://msdn2.microsoft.com/zh-cn/library/k3a58006(VS.80).aspx
3.4 反射的性能:
使用反射来调用类型或者触发方法,或者访问一个字段或者属性时clr 需 要做更多的工作:校验参数,检查权限等等,所以速度是非常慢的。所以尽量不要使用反射进行编程,对于打算编写一个动态构造类型(晚绑定)的应用程序,可以采取以下的几种方式进行代替:
1. 通过类的继承关系。让该类型从一个编译时可知的基础类型派生出来,在运行时生成该类 型的一个实例,将对其的引用放到其基础类型的一个变量中,然后调用该基础类型的虚方法。
2. 通过接口实现。在运行时,构建该类型的一个实例,将对其的引用放到其接口类型的一个变量中,然后调用该接口定义的虚方法。
3.通过委托实现。让该类型实现一个方法,其名称和原型都与一个在编译时就已知的委托相符。在运行时先构造该类型的实例,然后在用该方法的对象及名称构造出该委托的实例,接着通过委托调用你想要的方法。这个方法相对与前面两个方法所作的工作要多一些,效率更低一些
4. PetShop 4.0中所用到的反射
以DALFactory项目为例。通过引用IDAL,读取Web.Config里设置的程序集,动态创建类的实例,再返回给DALFactory.(关于工厂模式,我也无法理解得很透彻,个人认为工厂模式最大的好处还是在于 方便维护.)
在DALFactory的DataAccess类是一个sealed类,其中创建各种数据对象的方法,均为静态方法。对Web.Config和反射的运用使其达到抽象工厂的目的。如下所示
public sealed class DataAccess { // Look up the DAL implementation we should be using private static readonly string path = ConfigurationManager.AppSettings["WebDAL"]; private static readonly string orderPath = ConfigurationManager.AppSettings["OrdersDAL"]; private DataAccess() { } public static PetShop.IDAL.ICategory CreateCategory() { string className = path + ".Category"; return (PetShop.IDAL.ICategory)Assembly.Load(path).CreateInstance(className); }
由于命名空间统一存放在Web.Config里,类也是动态的创建,可以避免因为命名空间改变导致的一系列麻烦修改。
不过,反射这一特性会大量损耗性能,所以除非是维护需要,不然可通过其他方式解决。