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


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

    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)

    (完)

  • 相关阅读:
    由Highcharts加载提示想到的:我想要的别人已经做好了
    ajaxmin js压缩和VS(转1)
    ajaxmin js压缩和VS(转2)
    Silverlight客户端怎样获取外部参数
    SQL中的事务
    qt webkit 中文乱码问题 另辟蹊径
    密码学基础(1)
    使用jQuery的属性[attr]筛选
    Leetcode NO.19 Remove Nth Node From End Of List && 移除链表倒数第n个节点
    经典排序算法(一) —— Selection Sort 选择排序
  • 原文地址:https://www.cnblogs.com/xiashengwang/p/2593986.html
Copyright © 2020-2023  润新知