《CLR via C#》笔记——程序集的加载和反射(3)
2012-07-16 17:54 xiashengwang 阅读(1506) 评论(1) 编辑 收藏 举报五,使用反射发现类型的成员
1,发现类型成员
字段,构造器,方法,属性,事件和嵌套类都可以被定义为类型的成员。FCL定义了一个System.Reflection.MemberInfo的抽象基类,封装了一组所有类型成员都通用的属性。从MemberInfo派生的一组类,每个类都封装了与一个特定类型成员相关的属性。下面是这个类型的层次结构。
下面的程序演示如何查询一个类型的成员并显示与它们相关的信息。以下代码中,处理AppDomain中加载的所有程序集中的公共类型,对每个类型,调用GetMembers方法,并返回由MemberInfo派生对象构成的一个数组;对每个成员都显示他们的种类(字段,构造器,方法,属性等)以及它的字符串(ToString获取)。
private void DisplyAllMemberInfoOfAppDomain() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly a in assemblies) { WriteLine(0, "Assembly {0}", a); foreach (Type t in a.GetExportedTypes()) { WriteLine(1, "Type {0}", t); foreach (MemberInfo mi in t.GetMembers()) { string typeName = string.Empty; if (mi is Type) typeName = "(Nested) Type"; else if (mi is FieldInfo) typeName = "FieldInfo"; else if (mi is MethodInfo) typeName = "MethodInfo"; else if (mi is ConstructorInfo) typeName = "ConstructorInfo"; else if (mi is PropertyInfo) typeName = "PropertyInfo"; else if (mi is EventInfo) typeName = "EventInfo"; WriteLine(2, "{0}:{1}", typeName, mi); } } } } public void WriteLine(int ident, string format, params object[] args) { Console.WriteLine(new string(' ', ident * 3) + format, args); }
结果如下(部分):
Assembly mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Type System.Object MethodInfo:System.String ToString() MethodInfo:Boolean Equals(System.Object) MethodInfo:Boolean Equals(System.Object, System.Object) MethodInfo:Boolean ReferenceEquals(System.Object, System.Object) MethodInfo:Int32 GetHashCode() MethodInfo:System.Type GetType() ConstructorInfo:Void .ctor() Type System.Runtime.Serialization.ISerializable MethodInfo:Void GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext) Type System.Runtime.InteropServices._Exception MethodInfo:System.String ToString() MethodInfo:Boolean Equals(System.Object) MethodInfo:Int32 GetHashCode() MethodInfo:System.Type GetType() MethodInfo:System.String get_Message() MethodInfo:System.Exception GetBaseException() MethodInfo:System.String get_StackTrace()
由于MemberInfo类是成员层次的跟,所以要深入的研究一下。下面列出了MemberInfo的所有属性。
- Name:string属性,返回成员名称。对于嵌套类型,返回包容类型名称,后跟“+”,在跟嵌套类型。如:AssemblyTest.Form1+Cat
- MemberType:这是一个MemberType的枚举,返回成员的种类—字段,构造器,属性,方法等等。
- DeclaringType:Type属性,返回声明了成员的Type。
- ReflectedType:Type属性,返回获取该成员的Type。
- Module:Module属性,返回声明该成员的Module。
- MetaDataToken:Int32属性,返回该成员在模块中的元数据标记。
- GetCustomAttributes:方法,返回object[]。得到定制的attribute的实例。
- GetCustomAttributesData:返回IList<CustomAttributeData>,这个和GetCustomAttributes是类似的功能,主要用于一些以ReflectOlny方式加载的程序集,这些程序集的代码不能执行,而GetCustomAttributes会生成实例,所以只能用GetCustomAttributesData(4.0新增)。
- IsDefined:方法,返回bool型。如果指定的定制attribute至少有一个应用于该成员就返回true。
对于DeclaringType和ReflectedType,最容易弄混,下面来看一个例子,以帮助理解。
public sealed class MyType { public override string ToString() { return null; } }
执行以下代码,会得到什么结果呢?
MemberInfo[] members = typeof(MyType).GetMembers();
结果是这样的:
{System.Reflection.MemberInfo[5]} [0]: {System.String ToString()} [1]: {Boolean Equals(System.Object)} [2]: {Int32 GetHashCode()} [3]: {System.Type GetType()} [4]: {Void .ctor()}
为标识了ToString方法的MemberInfo元素查询其DeclareType属性,会返回MyType,因为MyType中声明了一个ToString。为Equals方法标识的MemberInfo元素查询其DeclareType属性,会返回System.Object,因为这个方法是在System.Object中定义的。ReflectionType总是返回MyType,因为调用GetMembers方法来执行反射时,指定的是这个类型。
在GetMembers返回的数组中,每个元素都是对层次结构中的一个具体类型的引用(除非指定了BindingFlags.DeclaredOnly标志)。虽然Type的GetMembers会返回类型的所有成员,但Type还提供了一些方法返回特定的类型成员。例如,Type提供了GetNestedTypes,GetFileds,GetConstructors,GetMethods,GetProperties
以及GetEvents方法。这些方法返回的都是一个数组,数组分别引用的Type,FiledInfo,ConstructorInfo,MethodInfo,PropertyInfo以及EventInfo对象。要查询类型的命名空间需要使用Type的Namespace属性。
基于一个类型,还可以发现它的接口。基于一个构造器,方法,属性访问器或者事件的添加/删除方法,可以调用GetParameters方法来获取由ParameterInfo构成的一个数组,从而了解成员的参数类型。还可以查询只读属性ReturnParameter来获得一个ParameterInfo对象,从而获取成员的返回值相关的信息。对于泛型类型和方法,可调用GetGenericArgument方法来获取类型的参数集合。针对上面的任何一项,都可以调用GetCustomAttributes方法来获取应用于它们的定制attribute。
2,BindingFlags:筛选返回的成员类型
可以调用Type提供的GetNestedTypes,GetFileds,GetConstructors,GetMethods,GetProperties以及GetEvents方法来查询类型的成员。调用上述的任何一个方法时,可以传递System.Reflection.BindingFlags枚举类型的一个实例。这个枚举类型标识了一组通过逻辑OR合并到一起的标志,目的是对返回的成员进行筛选,每个标志的意义如下:
Default:默认标志。
IgnorCase:返回与指定字符串匹配的成员(忽略大小写)。
DeclareOnly:只返回被反射的那个类型的成员,忽略继承成员。
Instance:返回实例成员。
Static:返回静态成员。
Public:返回公共成员。
NonPublic:返回非公共成员。
FlattenHierarchy:返回基类型定义的静态成员。
在返回一个成员集合的方法中,如果不指定BindingFlags,所有的方法都只返回公共成员。换言之,默认的设置是BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static。
注意Type还提供了GetNestedType,GetFiled,GetConstructor,GetMethod,GetProperty以及GetEvent方法。还允许方法传递一个String来标识要查找的成员,这时BindingFlags的IgnorCase标志就有用了。
3,发现类型的接口
为了获得类型继承的接口集合,可调用Type类型的FindInterfaces,GetInterface或GetInterfaces方法。所有这些方法都返回代表接口的Type对象。注意,这些方法扫描类型的继承层次结构,并返回指定类型及其基类型定义的所有接口。
判断一个类型的哪些成员实现了特定的接口有点复杂,因为多个接口可能定义了同一个方法。例如,IBookRetailer和IMusicRetailer接口都定义了一个叫Purchase的方法。为获得一个接口的MethodInfo对象,可以调用Type的GetInterfaceMap实例方法(传递接口类型作为实参),该方法返回一个System.Reflection.InterfaceMapping值类型实例,下面总结了InterfaceMapping的4个公共字段。
TargetType:Type类型,这是发出调用GetInterfaceMap的类型。
InterfaceType:Type类型,传给GetInterfaceMap的接口类型。
InterfaceMethods:MethodInfo[],一个数组,它的每一个元素对应于接口定义的方法信息。
TargetMethods:MethodInfo[],一个数组,它的每一个元素对应于实现接口的方法信息。
InterfaceMethods和TargetMethods是一一对应的,也就是说InterfaceMethods[0]标识接口的MethodInfo,而TargetMethods [0]标识类型定义的一个方法,它实现这个接口方法。下面是一个综合例子,演示如何发现接口和接口方法:
//定义两个接口,以备测试 internal interface IBookRetailer : IDisposable { void Purchase(); void ApplyDiscount(); } internal interface IMusicRetailer { void Purchase(); } //这个类实现由这个程序集定义的这2个接口 //以及有另一个程序集定义的1个接口 internal sealed class MyRetailer : IBookRetailer, IMusicRetailer, IDisposable { //IBookRetailer 的方法 void IBookRetailer.Purchase() { } public void ApplyDiscount() { } //IMusicRetailer 的方法 void IMusicRetailer.Purchase() { } //IDisposable 的方法 public void Dispose() { } //MyRetailer的一个方法(非接口方法) public void Purchase() { } } public void DisplayTypesInterface() { //查找在自己程序集中定义的,有MyRetailer实现的接口 //这需要将指向筛选器方法的委托传递给FindInterfaces Type t = typeof(MyRetailer); Type[] interfaces = t.FindInterfaces(new TypeFilter(TypeFilter), typeof(MyRetailer).Assembly); Console.WriteLine("MyRetailer implements the folowing interfaces(defined in this assembly):"); //显示每个接口的信息 foreach (Type i in interfaces) { Console.WriteLine(Environment.NewLine + "Interface:" + i); //获取映射到接口方法的类型方法 InterfaceMapping map = t.GetInterfaceMap(i); for (Int32 m = 0; m < map.InterfaceMethods.Length; m++) { //显示接口的方法 ,以及类型的哪个方法实现了这个接口方法 Console.WriteLine("{0} is implemented by {1}", map.InterfaceMethods[m], map.TargetMethods[m]); } } } //如果类型匹配筛选器条件,就返回true private bool TypeFilter(Type t, object filterCriteria) { //如果接口是由filterCriteria标识的程序集中定义的,就返回true return t.Assembly == (Assembly)filterCriteria; }
执行上面的代码,将得到下面的输出:
MyRetailer implements the folowing interfaces(defined in this assembly): Interface:AssemblyTest.Form1+IBookRetailer Void Purchase() is implemented by Void AssemblyTest.Form1.IBookRetailer.Purchase() Void ApplyDiscount() is implemented by Void ApplyDiscount() Interface:AssemblyTest.Form1+IMusicRetailer Void Purchase() is implemented by Void AssemblyTest.Form1.IMusicRetailer.Purchase()
4,调用类型的成员
知道如何发现类型的成员后,你可能像调用其中的一个成员。“调用”(invoke)的含义取决于要调用的成员的种类。调用一个FiledInfo,可以获取或设置字段的值;调用一个ConstructorInfo,可以向构造器传递实参,从而构造类型的一个实例;调用一个MethodInfo,可以通过传递实参来调用方法,并获得它的返回值;调用一个PropertyInfo,可以调用属性的get和set访问器;而调用一个EventInfo,可以添加或删除一个事件处理程序。
我们先来讨论如何调用一个方法,因为这是可以调用的最复杂的一个成员。Type类提供了一个InvokeMember方法,可以调用一个成员。下面讨论最常用的重载版本之一,其他的版本是相似的。
Public abstract class Type:MemberInfo,…{
Public object InvokeMember(
String name, //成员名称
BindingFlags invokeAttr, //如何查找成员
Binder binder, //如何匹配成员和实参
Object target, //要调用其成员的对象
Object[] args, //要传给方法的实参
CultureInfo culture); //某些绑定器使用的语言文化
}
调用InvokeMember时,它会在类型的成员中搜索一个匹配的类型,如果没有匹配的类型,它会抛出一个System.MissingMethodException,System.MissingFieldException,或者System.MissingMemberException异常。如果找到匹配的成员,InvokeMember会调用该成员,如果成员返回什么东西,InvokeMember会把它返回给你。如果成员不返回任何东西,InvokeMember返回null。如果调用的成员抛出异常,InvokeMember会捕捉这个异常,并抛出一个新的System.Reflection.TargetInvocatonException异常,TargetInvocatonException的InnerException属性包含了被调用方法抛出的实际异常。
在内部,InvokeMember会执行两个操作。首先,它必须选择要调用的一个恰当的成员—这称为绑定(binding)。其次,它必须调用实际的成员--这称为调用(invoking)。调用InvokeMember方法时,要为name参数传递一个string,指出希望InvokeMember方法访问的成员的名称。然而,类型可能提供了几个同名的成员。毕竟,一个方法可能有几个重载版本,或者一个方法和一个字段使用了一个相同的名称。当然,InvokeMember方法必须先绑定了一个成员,然后才能调用它。传给InvokeMember的所有参数(target参数除外)都用于帮助InvokeMember方法确定要绑定的成员。接下来,更深入的探讨这些参数。
Binder参数标识一个对象,它的类型是从System.Reflection.Binder抽象类派生的。从Binder派生的类型封装了InvokeMember方法选择成员时的规则。Binder基类型定义了一些抽象虚方法,比如BindToField,BindToMethod,ChangeType,ReorderArgumentArray,SelectMehtod和SelectProperty。在内部InvokeMember使用由“InvokeMember方法的binder参数”传递的Binder对象来调用这些方法。
Microsoft定义了一个名为System.DefaultBinder的在内部使用的具体类型。如果将null值传递给InvokeMember的binder参数,就会使用这个DefaultBinder对象。Type类型提供了一个名为DefaultBinder的公共静态属性。如有必要,可以查询来获得一个DefaultBinder对象的引用。
调用一个绑定器方法时,会向这些方法传递参数,从而帮助绑定器作出决定。首先,肯定要将目标成员的名称传递给绑定器的方法。然后,除了要将“传递给目标成员的所有参数类型”传给绑定器的方法,还要传递指定的BindingFlags。
除了这些标志位,绑定器还要检查InvokeMember方法的args参数传递的实参数量。通过实参数量进一步限定匹配。然后,绑定器检查实参的类型,进一步缩小范围。但要注意,在涉及实参的类型时,绑定器应用一些自动类型转换来获得更大的灵活性。比如,某个类型定义的一个方法可能要获取一个Int64实参。如果调用InvokeMember方法,为args参数传递一个数组引用,其中包含一个Int32的值,DefaultBinder会认为这个一个匹配项。
DefaultBinder支持如下所示的转换:
任何类型→它的基类型
任何类型→它实现的接口
值类型的实例→值类型实例的已装箱版本
如:Single→Double;Int32→Int64,Single,Double等等可以隐式转换的数据类型
可以利用BindingFlags来优化DefaultBinder的行为。
- ExactBinding 绑定器查找其形参类型与实参类型完全匹配的成员。(DefaultBinder忽略这个标志)
- OptionalParamBinding 如果一个成员的参数数量与传递的实参数量匹配,绑定器就会考虑这个成员。
InvokeMember方法的最后一个参数culture也可用于绑定。但是,DefaultBinder会完全忽略它。如果定义了一个自己的绑定器,可用这个参数来帮助进行实参类型的转换。
InvokeMember方法的target参数是对象要调用成员的一个对象的引用。要调用一个静态成员,这个参数应传递null值。
InvokeMember是一个很强大的方法。它允许调用一个方法,构造一个类型的实例(通过一个构造器方法),获取/设置一个字段或者获取/设置一个属性。为了告诉InvokeMember应采取那个行动,需要指定下面列出的BindingFlags。
- InvokeMethod 告诉InvokeMember调用一个方法。
- CreateInstance 告诉InvokeMember创建一个对象并调用其构造器。
- GetField 告诉InvokeMember获取一个字段的值。
- SetField 告诉InvokeMember设置一个字段的值。
- GetProperty 告诉InvokeMember获取一个属性的值。
- SetProperty 告诉InvokeMember设置一个属性的值。
上面列出的大多数标志都是互斥的—调用InvokeMember时,要选择,而且只能选择其中一个(只是针对上面这一组,你可以另外加上Public,Instance,Static等标志)。但是,同时指定GetField和GetProperty。在这种情况下,InvokeMember先查找匹配的字段,如果没有找到匹配的字段,就查找一个匹配的属性。类似的,同时指定SetField和SetProperty,它们按相同的方式匹配。如果指定了BindingFlags.CreateInstance标志,绑定器就知道它只能选择一个构造器方法。
重要提示:
根据目前为止讲到的东西,你可能认为使用反射很容易绑定并调用一个非公成员,使得应用程序代码可以访问私有成员,而这些成员一般是编译器禁止访问的。但是,反射使用“代码访问安全性”(Code Access Security,CAS)来确保这种能力不会被滥用或者盗用。
调用一个方法来绑定到一个成员时,CLR首先检查试图绑定的成员在编译时是否可见。如果可见,绑定就会成功。如果成员在编译是是不可见的,方法就会查询System.Security.Permissions.ReflectionPermission的TypeInformation位标志,如果该标志已设置,方法会绑定到成员,否则将抛出一个System.Security.SecurityException。调用一个方法来调用一个成员时,该方法将执行和绑定成员是相似的安全检查。但是这一次检查的是System.Security.Permissions.ReflectionPermission的MemberAccess位标志。如果设置,成员将会被调用(invoke);否则抛出System.Security.SecurityException。
如果程序集拥有完全的信任权限,就认为安全检查是成功的。允许正常的绑定和调用。
5,一次绑定,多次调用。
利用Type的InvokeMember方法,可以访问一个类型的所有成员。但是,应该注意的是,每次调用InvokeMember方法时,它都要先绑定到一个特定的成员,然后再调用它。如果每次调用一个成员都让绑定器选择恰当的成员,那么将是一个很费时的操作。如果频繁的进行这样的操作,应用程序的性能必然受到影响。因此,如果打算频繁访问一个成员,最好是一次绑定,多次调用。
调用Type的GetFields,GetConstructors,GetMethods,GetProperties,GetEvents或者其他类似的方法,可以返回对一个特定对象的引用。该对象类型提供了直接访问特定成员的方法。
PropertyInfo类型代表与属性有关的元数据信息:也就是说,PropertyInfo提供了CanRead,CanWrite和PropertyType自读属性。这些属性指出属性是只读还是只写的,以及属性的数据类型。PropertyInfo还有一个GetAccessors方法,它能返回一个由MethodInfo组成的数组:一个元素是get访问器方法,一个元素是set访问器方法。PropertyInfo提供的更有价值的方法是GetGetMethod和GetSetMethod,这两个方法都返回单个MethodInfo对象。Property的GetValue和SetValue 只是为了提供方便;在内部,它们会获取恰当的MethodInfo并调用它。为了支持有参属性,GetValue和SetValue方法提供了一个object[]类型的index参数。
绑定成员后如何调用成员,以下进行了归纳。
- FieldInfo 调用GetValue获取字段的值;调用SetValue设置字段的值。
- ConstructorInfo 调用Invoke构造类型的一个实例,并调用一个构造器。
- MethodInfo 调用Invoke调用类型的一个方法。
- PropertyInfo 调用GetValue调用属性的get访问器方法;调用SetValue调用属性的set访问器方法。
- EventInfo 调用AddEventHandler调用事件的add访问器方法;调用RemoveEventHandler调用事件的remove访问器方法。
EventInfo类型代表与事件相关的元数据信息。EventInfo提供了一个EventHandlerType只读属性,返回事件的基础委托的Type。EventInfo还提供了GetAddMethod和GetRemoveMethod方法,他们都返回MethodInfo。这个MethodInfo对应于事件的添加或删除委托的方法。要添加或删除一个委托,可以调用这些MethodInfo对象,也可调用EventInfo提供的更好的AddEventHandler和RemoveEventHandler方法。
注意:ConstructorInfo的Invoke方法,MethodInfo的Invoke方法,PropertyInfo的GetValue/SetValue方法都提供一个重载版本要求你传入一个Binder派生对象的引用以及一些BindingFlags。这可能会让你误以为这些方法要绑定到成员,但事实并不是这样。调用这些方法时,要在Binder派生对象的帮助下,对提供方法参数进行类型转换,例如将一个Int32实参转换为Int64,使已选中的方法可以被正常调用。至于BindingFlags参数,这里唯一可以传递的标志就是BindingFlags.SuppressChangeType。但是绑定器完全可以忽略这个标志,幸好,DefaultBinder类会注意到这个标志。DefalutBinder看到这个标志,它不会转换任何实参。如果使用了这个标志,而传入的实参和方法调用期望的实参不匹配,就会抛出一个ArgumentException。
经测验,发现SurpressChangeType标志无效。Jeffrey在这里又笔误了,按照他的说法,“ExactBinding和SurpressChangeType一前一后调用,或是在Type的InvokeMember方法中同时使用这两个标志应该可以成功。”。但查看Msdn后发现对于SurpressChangeType标志,微软并没有真正实现(Not implemented);并且ExactBinding只支持自定义的Binder,DefaultBinder会忽略这个标志。(伤不起呀,为求真理,在这里耗费了二小时,也不知Jeffrey用的什么版本的.Net),可以查看网址:http://msdn.microsoft.com/en-us/library/system.reflection.bindingflags.aspx。
下面的示例演示了用反射来访问类型成员的各种方式,分别用4个方法演示了用不同的方式来做相同的事情。
- UseInvokeMemberToBindAndInvokeTheMember 方法,演示了如何用Type的InvokeMember来绑定并调用一个成员。
- BindToMemberThenInvokeTheMember方法,演示了如何绑定到一个成员,并在以后调用它。如果你打算多次调用同一成员,这个技术执行效率将会更高。
- BindToMemberCreateDelegateToMemberThenInvokeTheMember方法,演示了如何绑定到一个对象或成员,然后创建一个委托来引用该对象或成员。通过委托来调用的速度非常快。如果想在相同的对象上多次调用相同的成员,这个技术比上一个技术执行效率还要高。
- UseDynamicToBindAndInvokeTheMember方法,演示了如何使用C#的dynamic基元类型来简化访问成员时的语法。除此之外,如果打算在相同类型的不同对象上调用相同的成员,这个技术提供了不错的性能,因为针对类型,绑定只会发生一次。而且可以缓存起来,以后多次调用时速度会非常快。可以用这个技术调用不同类型的对象的成员。
internal sealed class SomeType { private Int32 m_someField; public SomeType(ref Int32 x) { x = x*2; } public override string ToString() { return m_someField.ToString(); } public Int32 SomeProp { get { return m_someField; } set { if (value < 1) throw new ArgumentOutOfRangeException("value"); m_someField = value; } } public event EventHandler SomeEvent; private void NoCompilerWarnings() { SomeEvent.ToString(); } } private const BindingFlags c_bf = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; private void InvokeTheMember() { Type t = typeof(SomeType); UseInvokeMemberToBindAndInvokeTheMember(t); Console.WriteLine(); BindToMemberThenInvokeTheMember(t); Console.WriteLine(); BindToMemberCreateDelegateToMemberThenInvokeTheMember(t); Console.WriteLine(); UseDynamicToBindAndInvokeTheMember(t); } private void UseInvokeMemberToBindAndInvokeTheMember(Type t) { Console.WriteLine("UseInvokeMemberToBindAndInvokeTheMember"); //create type's instance object[] args = new object[] { 12 }; Console.WriteLine("x before constructor called:" + args[0]); object obj = t.InvokeMember(null, c_bf | BindingFlags.CreateInstance, null, null, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]); //write and read a field t.InvokeMember("m_someField", c_bf | BindingFlags.SetField, null, obj, new object[] { 5 }); Int32 v = (Int32)t.InvokeMember("m_someField", c_bf | BindingFlags.GetField, null, obj, null); Console.WriteLine("someField: " + v); //invoke a method string s = (string)t.InvokeMember("ToString", c_bf | BindingFlags.InvokeMethod, null, obj, null); Console.WriteLine("ToString: " + s); //write and read a property try { t.InvokeMember("SomeProp", c_bf | BindingFlags.SetProperty, null, obj, new object[] { 0 }); } catch (TargetInvocationException e) { if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw; Console.WriteLine("Property set catch."); } t.InvokeMember("SomeProp", c_bf | BindingFlags.SetProperty, null, obj, new object[] { 2 }); v = (Int32)t.InvokeMember("SomeProp", c_bf | BindingFlags.GetProperty, null, obj, null); Console.WriteLine("SomeProp: " + v); //add and remove the event EventHandler eh = new EventHandler(EventCallBack); t.InvokeMember("add_SomeEvent", c_bf | BindingFlags.InvokeMethod, null, obj, new object[] { eh }); t.InvokeMember("remove_SomeEvent", c_bf | BindingFlags.InvokeMethod, null, obj, new object[] { eh }); } private void EventCallBack(object sender, EventArgs e) { } private void BindToMemberThenInvokeTheMember(Type t) { Console.WriteLine("BindToMemberThenInvokeTheMember"); //create instance ConstructorInfo ctor = t.GetConstructor(new[] { Type.GetType("System.Int32&") }); //注意,上面这句也可以写成下面这样 //ConstructorInfo ctor = t.GetConstructor(new[] { typeof(Int32).MakeByRefType() }); object[] args = new object[] { 12 }; Console.WriteLine("x before constructor called:" + args[0]); object obj = ctor.Invoke(args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]); //write and read a field FieldInfo fi = t.GetField("m_someField", c_bf); fi.SetValue(obj, 5); Int32 v = (Int32)fi.GetValue(obj); Console.WriteLine("someField: " + v); //invoke a method MethodInfo mi = t.GetMethod("ToString", c_bf); string s = (string)mi.Invoke(obj, null); Console.WriteLine("ToString: " + s); //write and read a property PropertyInfo pi = t.GetProperty("SomeProp"); try { pi.SetValue(obj, 0, null); } catch (TargetInvocationException e) { if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw; Console.WriteLine("Property set catch."); } pi.SetValue(obj, 2, null); v = (Int32)pi.GetValue(obj, null); Console.WriteLine("SomeProp: " + v); //add and remove the event EventHandler eh = new EventHandler(EventCallBack); EventInfo ei = t.GetEvent("SomeEvent"); ei.AddEventHandler(obj, eh); ei.RemoveEventHandler(obj, eh); } private void BindToMemberCreateDelegateToMemberThenInvokeTheMember(Type t) { Console.WriteLine("BindToMemberCreateDelegateToMemberThenInvokeTheMember"); //create instance(can't create delegate for constructor object[] args = new object[] { 12 }; Console.WriteLine("x before constructor called:" + args[0]); object obj = Activator.CreateInstance(t, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]); //can't create delegate for a field //invoke method MethodInfo mi = t.GetMethod("ToString", c_bf); var toString = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>),obj, mi); string s = toString(); Console.WriteLine("ToString: " + s); //read/wirte property PropertyInfo pi = t.GetProperty("SomeProp", typeof(Int32)); var setSomeProp = (Action<Int32>)Delegate.CreateDelegate(typeof(Action<Int32>),obj, pi.GetSetMethod()); try { setSomeProp(0); } catch (ArgumentOutOfRangeException e) { Console.WriteLine("Property set catch."); } setSomeProp(2); var getSomeProp = (Func<Int32>)Delegate.CreateDelegate(typeof(Func<Int32>), obj, pi.GetGetMethod()); Console.WriteLine(getSomeProp()); //add/remove event EventInfo ei = t.GetEvent("SomeEvent", c_bf); var assSomeEvent = (Action<EventHandler>)Delegate.CreateDelegate(typeof(Action<EventHandler>),obj, ei.GetAddMethod()); assSomeEvent(EventCallBack); var removeSomeEvent = (Action<EventHandler>)Delegate.CreateDelegate(typeof(Action<EventHandler>),obj, ei.GetRemoveMethod()); removeSomeEvent(EventCallBack); } private void UseDynamicToBindAndInvokeTheMember(Type t) { Console.WriteLine("UseDynamicToBindAndInvokeTheMember"); //create instance(can't create delegate for constructor object[] args = new object[] { 12 }; Console.WriteLine("x before constructor called:" + args[0]); dynamic obj = Activator.CreateInstance(t, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]); //read/set a field try { obj.m_someField = 5; Int32 v = (Int32)obj.m_someField; Console.WriteLine("someField: " + v); } catch (RuntimeBinderException e) { Console.WriteLine("Failed to access private fielc: " + e.Message); } //invoke method string s = obj.ToString(); Console.WriteLine("ToString: " + s); //read/write property try { obj.SomeProp = 0; } catch (ArgumentOutOfRangeException e) { Console.WriteLine("Property set catch"); } obj.SomeProp = 2; Int32 val = (Int32)obj.SomeProp; Console.WriteLine("SomeProp: " + val); //add/remove event obj.SomeEvent += new EventHandler(EventCallBack); obj.SomeEvent -= new EventHandler(EventCallBack); }
运行结果大致如下:
UseInvokeMemberToBindAndInvokeTheMember x before constructor called:12 Type:AssemblyTest.Form1+SomeType x after constructor returns:24 someField: 5 ToString: 5 Property set catch. SomeProp: 2 BindToMemberThenInvokeTheMember x before constructor called:12 Type:AssemblyTest.Form1+SomeType x after constructor returns:24 someField: 5 ToString: 5 Property set catch. SomeProp: 2 BindToMemberCreateDelegateToMemberThenInvokeTheMember x before constructor called:12 Type: AssemblyTest.Form1+SomeType x after constructor returns: 24 ToString: 0 Property set catch. 2 UseDynamicToBindAndInvokeTheMember x before constructor called:12 Type: AssemblyTest.Form1+SomeType x after constructor returns: 24 Failed to access private fielc: 'AssemblyTest.Form1.SomeType.m_someField' ToString: 0 Property set catch SomeProp: 2
关于引用类型参数的写法,本例用了Type.GetType("System.Int32&"),其中&代表了引用,也可以用下面这种写法。typeof(Int32).MakeByRefType()。
6,使用绑定句柄来减少进程的内存消耗。
许多应用程序中,绑定了一组类型(Type)或者类型成员(从MemberInfo派生),并将这些对象保存在某种形式的一个集合中。以后,会搜索这个集合,查找特定的对象,然后调用这个对象。这是一个很好的机制,但是有个小问题:Type和MemberInfo派生的对象需要大量的内存。如果一个应用程序容纳了太多这样的类,但只是偶尔用一下它们,应用程序的内存就会急剧增长,对应用程序的性能产生影响。
在内部,CLR用一种更精简的形式来表示这种信息。CLR之所以为应用程序创建这些对象,只是为了简化开发人员的工作。CLR在运行时并不需要这些大对象。如果需要缓存大量Type和MemberInfo派生对象,开发人员可以使用运行时句柄(runtime handle)来代替对象,从而减少工作集(占用的内存)。FCL定义了3个运行时句柄类型(都在System命名空间中),RuntimeTypeHandle,RuntimeFieldHandle,RumtimeMethodHandle。三个类型都是值类型,他们只包含了一个字段,也就是一个IntPtr;这样一来,这些类型的实例就相当省内存。ItPtr字段是一个句柄,它引用了AppDomain的Loader堆中的一个类型,字段或方法。转换方法:
- Type→RuntimeTypeHandle,通过查询Type的只读字段属性TypeHandle。
- RuntimeTypeHandle→Type,通过调用Type的静态方法GetTypeFromHanlde。
- FieldInfo→RuntimeFieldHandle,通过查询FieldInfo的实例只读字段FieldHandle。
- RuntimeFieldHandle→FieldInfo,通过调用FieldInfo的静态方法GetFieldFromHandle。
- MethodInfo→RuntimeMethodHandle,通过查询MethodInof的实例只读字段MethodHandle。
- RuntimeMethodHandle→MethodInfo,通过调用MethodInfo的静态方法GetMethodFromHandle。
下面的示例获取许多的MethodInfo对象,把它们转化成RuntimeMethodHandle实例,并演示转换前后的内存差异。
private void UseRuntimeHandleToReduceMemory() { Show("Before doing anything"); List<MethodBase> methodInfos = new List<MethodBase>(); foreach (Type t in typeof(object).Assembly.GetExportedTypes()) { if (t.IsGenericType) continue; MethodBase[] mbs = t.GetMethods(c_bf); methodInfos.AddRange(mbs); } Console.WriteLine("# of Methods={0:###,###}", methodInfos.Count); Show("After building cache of MethodInfo objects"); List<RuntimeMethodHandle> methodHandles = new List<RuntimeMethodHandle>(); methodHandles = methodInfos.ConvertAll<RuntimeMethodHandle>(m => m.MethodHandle); Show("Holding MethodInfo and RuntimeMethodHandle"); GC.KeepAlive(methodHandles); methodInfos = null; Show("After freeing MethodInfo objects"); methodInfos = methodHandles.ConvertAll<MethodBase>(r => MethodBase.GetMethodFromHandle(r)); Show("Size of heap after re-creating methodinfo objects"); GC.KeepAlive(methodHandles); GC.KeepAlive(methodInfos); methodInfos = null; methodHandles = null; Show("after freeing MethodInfo and MethodHandle objects"); }
结果如下:
Heap Size = 114,788 - Before doing anything # of Methods=10,003 Heap Size = 2,205,652 - After building cache of MethodInfo objects Heap Size = 2,245,744 - Holding MethodInfo and RuntimeMethodHandle Heap Size = 2,171,976 - After freeing MethodInfo objects Heap Size = 2,327,516 - Size of heap after re-creating methodinfo objects Heap Size = 247,028 - after freeing MethodInfo and MethodHandle objects
7,几种反射调用的时间性能测试。
下面分别对以下四种方式:直接调用,InvokeMember,MethodInfo+Invoke,MethodInfo+Delegate+Invoke,
Activator+dynamic进行了对比。经测试,Delegate和dynamic的方式已经接近了直接调用的性能。InvokeMember最慢,应尽量避免使用。MethodInfo+Invoke比InvokeMember略好一些。
private void TestTimeOfReflect() { int LoopCount = 100000; Stopwatch sw = new Stopwatch(); Console.WriteLine("Test Loop Count : {0:N0}", LoopCount); //DirectCall sw.Start(); People p = new People("zhang"); for (int k = 0; k < LoopCount; k++) { p.ToString(); } sw.Stop(); ShowTimeAndMemory("DirectCall", sw.ElapsedMilliseconds); Type t = typeof(People); object obj; // InvokeMember sw.Restart(); obj = t.InvokeMember(null, c_bf | BindingFlags.CreateInstance, null, null, new object[] { "zhang" }); for (int k = 0; k < LoopCount; k++) { t.InvokeMember("ToString", c_bf | BindingFlags.InvokeMethod, null, obj, null); } sw.Stop(); ShowTimeAndMemory("InvokeMember", sw.ElapsedMilliseconds); //Cache + Invoke sw.Restart(); ConstructorInfo ci = t.GetConstructor(new Type[] { typeof(string) }); obj = ci.Invoke(new object[] { "zhang" }); MethodInfo mi = t.GetMethod("ToString", c_bf); for (int k = 0; k < LoopCount; k++) { mi.Invoke(obj, null); } sw.Stop(); ShowTimeAndMemory("Cache MethodInfo + Invoke", sw.ElapsedMilliseconds); //Cache + delegate + Invoke sw.Restart(); ci = t.GetConstructor(new Type[] { typeof(string) }); obj = ci.Invoke(new object[] { "zhang" }); mi = t.GetMethod("ToString", c_bf); var toString = (Func<string>)Delegate.CreateDelegate(typeof(Func<string>), obj, mi); for (int k = 0; k < LoopCount; k++) { toString(); } sw.Stop(); ShowTimeAndMemory("Cache MethodInfo + Create Delegate + Invoke", sw.ElapsedMilliseconds); //dynamic Invoke sw.Restart(); dynamic dyObj = Activator.CreateInstance(typeof(People), new object[] { "zhang" }); for (int k = 0; k < LoopCount; k++) { dyObj.ToString(); } sw.Stop(); ShowTimeAndMemory("Activator + dynamic(C#)", sw.ElapsedMilliseconds); } private void ShowTimeAndMemory(string action, long time) { Console.WriteLine("{0} (times= {1:N0}ms)", action, time); } 测试结果(循环十万次): Test Loop Count : 100,000 DirectCall (times= 10ms) InvokeMember (times= 300ms) Cache MethodInfo + Invoke (times= 189ms) Cache MethodInfo + Create Delegate + Invoke (times= 9ms) Activator + dynamic(C#) (times= 10ms)
(完)