雁过请留痕...
代码改变世界

《CLR via C#》笔记——程序集的加载和反射(3)

2012-07-16 17:54  xiashengwang  阅读(1488)  评论(1编辑  收藏  举报

五,使用反射发现类型的成员

1,发现类型成员

  字段,构造器,方法,属性,事件和嵌套类都可以被定义为类型的成员。FCL定义了一个System.Reflection.MemberInfo的抽象基类,封装了一组所有类型成员都通用的属性。从MemberInfo派生的一组类,每个类都封装了与一个特定类型成员相关的属性。下面是这个类型的层次结构。

  下面的程序演示如何查询一个类型的成员并显示与它们相关的信息。以下代码中,处理AppDomain中加载的所有程序集中的公共类型,对每个类型,调用GetMembers方法,并返回由MemberInfo派生对象构成的一个数组;对每个成员都显示他们的种类(字段,构造器,方法,属性等)以及它的字符串(ToString获取)。

View Code
        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。

2BindingFlags:筛选返回的成员类型

  可以调用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]标识类型定义的一个方法,它实现这个接口方法。下面是一个综合例子,演示如何发现接口和接口方法:

View Code
        //定义两个接口,以备测试
         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基元类型来简化访问成员时的语法。除此之外,如果打算在相同类型的不同对象上调用相同的成员,这个技术提供了不错的性能,因为针对类型,绑定只会发生一次。而且可以缓存起来,以后多次调用时速度会非常快。可以用这个技术调用不同类型的对象的成员。
View Code
       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);
        }

运行结果大致如下:

View Code
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实例,并演示转换前后的内存差异。

View Code
      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");
        }

结果如下:

View Code
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略好一些。

View Code
      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)

 

(完)