第五节:使用反射发现类型成员

到目前为止,本章的重点一直放在发射机制中用于构建动态可扩展应用程序所需的那些方面,包括程序集加载、类型发现以及对象构造。为了获得较好的性能和编译时的类型安全性,应尽量避免使用发射。在动态可扩展应用程序的情况下,构造好一个对象之后,宿主代码一般要将对象转型为编译时已知的一个接口或者基类。这样一来,访问对象的成员时,就可以获得较高的性能,而且可以确保编译时的类型安全性。

在本章剩余的部分,我们将从其他角度讨论反射,目的是发现并调用类型的成员,这种发现并调用类型成员的能力一般用于开发工具和实用程序,他们需要查找特定的编程模式或者对特定成员的使用,以便对程序集进行分析。这种工具/实用程序的例子包括ILDasm.exe,FxCopCmd.exe以及VS的Window窗体和Web窗体设计器。另外,一些类库也利用了这个能力来发现和调用一个类型的成员,从而以简单的方式为开发人员提供丰富的功能。这种类库的例子包括执行序列化/反序列化和简单数据绑定的类库。

  1. 发现类型成员

字段、构造器、方法、属性、事件和嵌套类型都可以被定义成一个类型的成员。FCL包含了一个名为System.Reflection.MemberInfo的类型。这是一个抽象基类,封装了一组所有类型成员都通用的属性。

       

下面示例演示如何查询一个类型的成员并显示与他们相关的一些信息。在以下代码中,处理的是调用AppDomain中加载的所有程序集定义的所有公共类型。对于每一个类型,都调用GetMembers方法,并返回由MemberInfo派生对象构成的一个数组;每个对象都引用类型中定义的一个成员。BindingFlags变量bf被传给GetMembers方法,告诉该方法时返回哪些种类的成员。后面讲进一步讨论BindingFlags。对每个成员都显示他的种类(字段、构造器、方法和属性等)以及它的字符串(调用ToString来获取)。

 

static void Main(string[] args)
        {
            //遍历这个AppDomain中加载的所有程序集
            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);

                    const BindingFlags bf = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;

                    foreach (MemberInfo mi in t.GetMembers(bf))
                    {
                        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);
                    }
                }
            }
        }
        private static void WriteLine(Int32 indent, String format, params Object[] args)
        {
            Console.WriteLine(new String(' ', 3 * indent) + format, args);
        }

  

由于MemberInfo类是成员层次结构的根,所以有必要更深入的研究一下它。下图展示了MemberInfo类提供的几个只读属性和方法。这些属性和方法是一个类型的所有成员都是通用的。不要忘了System.Type是从MemberInfo派生的。所以Type也提供了下面列表的所有属性。

列出的大多数属性都很容易理解。但是,开发人员通常混淆DeclaringType和ReflectedType属性。为了帮助你完全理解这两个属性,我定义了下面这个类型:

 

 public sealed class MyType
    {
        public override string ToString()
        {
            return null;
        }
    }

  

执行以下代码,会产生什么结果呢?

 

        MemberInfo [] memberInfo =typeof(MyType).GetMembers();

 

memberInfo是一个数组的引用,数组中的每个元素都标识由MyType及其任何基类(例如System.Object)定义的一个公共成员。如果为标识ToString方法的MemberInfo元素查询DeclaringType属性,会返回MyType,因为MyType声明了一个ToString方法。另一方面,如果为标识Equals方法的MemberInfo元素查询其DeclaringType属性,会返回System.Object,因为Equals是System.Object声明的,而非由MyType声明。ReflectedType属性则总是返回MyType,因为调用GetMembers方法来执行反射时,指定的是这个类型。

 

在调用GetMembers返回的数组中,每个元素都是对层次结构中的一个具体类型的引用(除非指定BindingFlags.DeclaredOnly标志)。虽然Type的GetMembers方法会返回类型的所有成员,但Type还提供了一些方法能够返回特定的成员类型。例如,Type提供了

GetNestedTypes,GetFields,GetConstructors,GetMethods,GetProperties以及GetEvents方法。这些方法返回的都是一个数组,数组引用的分别是Type、FieldInfo、

ConstructorInfo、MethodInfo、PropertyInfo以及EventInfo对象。

 

基于一个基类,还可发现它实现的接口。基于一个构造器、方法、属性访问器方法或者事件的添加/删除方法,可以调用GetParameters方法来获取由ParameterInfo对象构成的一个数组,从而了解成员的参数类型。还可查询只读属性ReturnParameter来获取一个ParameterInfo对象,从而获取与成员返回值相关的信息,对于泛型类型或方法,可调用GetGenericArguments方法来获取类型参数集合。最后,针对上诉任何一项,都可以调用GetCustomAttributes方法来获取应用于他们自身定义的attribute集合。

 

  1. BindingFlags:筛选返回的成员种类

 

可以调用Type的GetNestedTypes,GetFields,GetConstructors,GetMethods,GetProperties或者GetEvents方法来查询一个属性的成员。调用上诉任何一个方法时,可以传递System.Reflection.BindingFlags枚举类型的一个实例。这个枚举类型标示了一组通过逻辑OR运算合并到一起的位标志,目的是对这些方法返回的成员进行筛选。

 

    public enum BindingFlags

    {

        // 摘要:

        //     不指定绑定标志。

        Default = 0,

        //

        // 摘要:

        //     指定当绑定时不应考虑成员名的大小写。

        IgnoreCase = 1,

        //

        // 摘要:

        //     指定只应考虑在所提供类型的层次结构级别上声明的成员。不考虑继承成员。

        DeclaredOnly = 2,

        //

        // 摘要:

        //     指定实例成员将包括在搜索中。

        Instance = 4,

        //

        // 摘要:

        //     指定静态成员将包括在搜索中。

        Static = 8,

        //

        // 摘要:

        //     指定公共成员将包括在搜索中。

        Public = 16,

        //

        // 摘要:

        //     指定非公共成员将包括在搜索中。

        NonPublic = 32,

        //

        // 摘要:

        //     指定应返回层次结构上的公共静态成员和受保护的静态成员。不返回继承类中的私有静态成员。静态成员包括字段、方法、事件和属性。不返回嵌套类型。

        FlattenHierarchy = 64,

        //

        // 摘要:

        //     指定要调用一个方法。它不能是构造函数或类型初始值设定项。

        InvokeMethod = 256,

        //

        // 摘要:

        //     指定“反射”应该创建指定类型的实例。调用与给定参数匹配的构造函数。忽略提供的成员名。如果未指定查找类型,将应用 (Instance |Public)。调用类型初始值设定项是不可能的。

        CreateInstance = 512,

        //

        // 摘要:

        //     指定应返回指定字段的值。

        GetField = 1024,

        //

        // 摘要:

        //     指定应设置指定字段的值。

        SetField = 2048,

        //

        // 摘要:

        //     指定应返回指定属性的值。

        GetProperty = 4096,

        //

        // 摘要:

        //     指定应设置指定属性的值。对于 COM 属性,指定此绑定标志与指定 PutDispProperty 和 PutRefDispProperty 是等效的。

        SetProperty = 8192,

        //

        // 摘要:

        //     Specifies that the PROPPUT member on a COM object should be invoked.PROPPUT

        //     specifies a property-setting function that uses a value.如果属性同时具有 PROPPUT

        //     和 PROPPUTREF,而且需要区分调用哪一个,请使用 PutDispProperty。

        PutDispProperty = 16384,

        //

        // 摘要:

        //     Specifies that the PROPPUTREF member on a COM object should be invoked.PROPPUTREF

        //     specifies a property-setting function that uses a reference instead of a

        //     value.如果属性同时具有 PROPPUT 和 PROPPUTREF,而且需要区分调用哪一个,请使用 PutRefDispProperty。

        PutRefDispProperty = 32768,

        //

        // 摘要:

        //     指定提供参数的类型必须与对应形参的类型完全匹配。如果调用方提供一个非空 Binder 对象,则“反射”将引发异常,因为这意味着调用方正在提供的 BindToXXX

        //     实现将选取适当的方法。

        ExactBinding = 65536,

        //

        // 摘要:

        //     未实现。

        SuppressChangeType = 131072,

        //

        // 摘要:

        //     返回其参数计数与提供参数的数目匹配的成员集。此绑定标志用于所带参数具有默认值的方法和带变量参数 (varargs) 的方法。此标志应只与 System.Type.InvokeMember(System.String,System.Reflection.BindingFlags,System.Reflection.Binder,System.Object,System.Object[],System.Reflection.ParameterModifier[],System.Globalization.CultureInfo,System.String[])

        //     一起使用。

        OptionalParamBinding = 262144,

        //

        // 摘要:

        //     在 COM 互操作 中用于指定可以忽略成员的返回值。

        IgnoreReturn = 16777216,

}

 

在返回一个成员集合的所有方法中,每个都有一个不获取任何实参的重载版本。如果不传递BindingFlags实参,所有的方法都只返回公共成员。换言之,默认设置是:

BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static。

 

注意,Type还定义了GetMethod,GetNestedType,GetField,GetConstructor,GetMethod,GetProperty,GetEvent方法。这些方法允许传递一个String来标识要查找一个成员的名称,在这个时候,BindingFlags的IgnoreCase标志就有用了。

 

3.   发现类型的接口

 

为了获得一个类型继承的接口集合,可以调用Type类型的FindInterfaces ,GetInterface或 GetInterfaces方法所有这些方法都返回代表接口的Type对象。注意,这些方法扫描类型的继承层次关系,并返回在指定类型以及所有基类型上定义的所有接口。

 

判断一个类型的哪些成员实现了一个特定接口有些复杂,因为多个接口可能定义同一个方法。例如,IBookRetailer和ImusicRetailer接口可能都定义了一个名为Purchase的方法。为获得一个特定接口的MethodInfo对象,可调用Type的GetInterfaceMap实例方法(传递接口类型作为参数)。该方法返回InterfaceMapping一个实例,

 

    public struct InterfaceMapping

    {

        // 摘要:

        //     显示在接口上定义的方法。

        public MethodInfo[] InterfaceMethods;

        //

        // 摘要:

        //     显示表示接口的类型。

        public Type InterfaceType;

        //

        // 摘要:

        //     显示实现接口的方法。

        public MethodInfo[] TargetMethods;

        //

        // 摘要:

        //     表示用于创建接口映射的类型。

        public Type TargetType;

    }

 

InterfaceMethods和TargetMethods数组是互为对应的。也就是说,InterfaceMethods[0]标识接口的MethodInfo,而TargetMethods[0]标识是类型定义的方法,它实现了这个接口方法。以下演示了如何发现类型定义的接口和接口方法:

interface IBookRetailer : IDisposable
    {
        void Purchase();
        void ApplyDiscount();
    }
    interface IMusicRetailer
    {
        void Purchase();
    }
    sealed class MyRetailer : IBookRetailer, IMusicRetailer, IDisposable
    {
        void IBookRetailer.Purchase() { }

        public void ApplyDiscount() { }

        void IMusicRetailer.Purchase() { }

        //IDisposable的方法
        public void Dispose() { }

        public void Purchase() { }
    }
  static void Main(string[] args)
        {
            Type t = typeof(MyRetailer);

            Type[] interfaces = t.FindInterfaces(TypeFilter, typeof(Program).Assembly);

            foreach (Type i in interfaces)
            {
                Console.WriteLine("\nInterface" + 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]);
                }
            }
        }

  

  1. 调用类型的成员

知道如何发现类型定义的成员之后,你可能想调用其中的成员,”调用”的含义取决于要调用成员的种类。调用一个FieldInfo,可获取或设置字段的值;调用一个ConstructorInfo,可以想构造器传递参数,从而构造类型的一个实例;调用一个MethodInfo,可以通过传递实参来调用方法,并获得它的返回值;调用一个PropertyInfo,可以调用属性的get和set访问器方法。而调用一个EventInfo可以添加或删除一个事件处理程序。

 

我们首先讨论了如何调用一个方法,因为这是可以调用的最复杂的一个成员。我们接着讨论如何调用其他成员。Type类提供了一个InvokeMember方法,可通过它调用一个成员。InvokeMember方法有几个重载版本。

 

public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, CultureInfo culture);

 

调用InvokeMember时,它会在类型的成员中搜索一个匹配的成员。如果没有找到匹配的成员,会抛出一个MissingMethodException,MissingFieldException或MissingMemberException异常。如果找到一个匹配的类型,InvokeMember方法会调用成员。如果成员返回什么东西,InvokeMember会把它返回给你。如果成员不返回任何东西,InvokeMember将返回null。如果调用的成员抛出异常,InvokeMember会捕捉异常,并抛出一个新的TargetInvocationException。TargetInvocationException对象的InnerException属性包含了由被调用方法抛出的实际异常。

 

在内部,InvokeMember会执行两个操作。首先,它必须选择要调用一个恰当的成员:这成为绑定。其次,它必须实际调用成员:这成为调用。调用InvokeMember方法时,要为name参数传递一个String,指出希望InvokeMember方法绑定的成员的名称。然而,类型可能提供几个同名的成员。毕竟,一个方法可能有几个重载版本,或者一个方法和一个字段使用了相同的名称。当然,InvokeMember方法必须先绑定一个成员,然后才能调用成员。传给InvokeMember的所有参数都用于帮助InvokeMember方法确定要绑定的成员。让我们更深入的讨论一下这些参数。

 

Binder参数标识一个对象,它的类型是从Binder抽象类型派生的。从Binder派生的类型封装了InvokeMember方法选择一个成员时的规则。Binder基类定义了一些抽象虚方法,比如BindToField,BindToMethod,ChangeType,ReorderArgumentArray,SelectMethod,SelectProperty。在内部,InvokeMember使用由” InvokeMember方法的binder参数”传递的binder对象来调用这些方法。

 

微软定义了System.DefaultBinder的在内部使用的具体类型,它未在文档中记录。这个类型派生自Binder。DefaultBinder类型时和FCL一起发布的,微软预计几乎每个人都要用到这个绑定器。一些编译器厂家会定义自己的Binder派生类型,并通过一个运行时库来发布。他们的编译器在生成代码时,会用到这个库。如果将null传给InvokeMember的binder参数,它会使用一个DefaultBinder对象。Type类型提供了一个名为DefaultBinder静态属性只读的,如果有必要,可查询该属性来获得对一个DefaultBinder对象的引用。

 

调用一个绑定器对象的方法时,会向这些方法传递参数,从而帮助绑定器做出决定。首先,要将目标成员的名称传给绑定器的方法。然后,除了要”传给目标成员参数的类型”传给绑定器的方法,还要传递指定的BindingFlags。

 

前面讨论的BindingFlags,是用于告诉绑定器要在搜索中包含哪些成员。

 

除了这些位标志,绑定器还要检查通过InvokeMember方法args参数传递的实参数量。实参数量进一步限制了可能的匹配项。然后,绑定器检查实参的类型,进一步缩小范围。但要注意,在涉及实参类型时,绑定器会应用一些自动类型转换来获得更大的灵活性。例如,某个类型定义了一个方位可能获得Int64实参,如果调用InvokeMember方法,为args参数传递一个数组引用,数组中包含一个Int32的值,DefaultBinder会认为这是一个匹配项。调用方法时,该Int32值被转换为一个Int64值。

可利用另外两个BindingFlags来优化DefaultBinder的行为。

 

ExactBinding  绑定器查找其形参与传递的实参完全匹配的成员。

 

OptionalParamBinding  如果一个成员的参数数量与传递的参数数量匹配,绑定器就会考虑这个成员,如果一些成员的参数有默认值,或一些方法要获取数量可变的参数,这些标志就有用了,

 

InvokeMember方法的最后一个参数culture也用于绑定。但是,DefaultBinder完全会忽略该参数。如果定义了自己的绑定器,可用这个参数来绑定实参类型的转化。例如,调用者可传递一个值为”1,23”的string类型。绑定器可能检查该字符串,用指定的culture解析它,将参数的类型转化为一个Single(如果语言文化是”de-DE”),或者继续将实参看成一个String。

 

到此为止,我们已经讨论了InvokeMember方法的所有实参与绑定有关的参数。还有讨论一个参数target,该参数是对想要调用器的成员的一个对象的引用,要调用一个类型的静态成员时,这个参数应传递为null。

 

 

InvokeMember是一个很强大的方法。它允许调用一个方法、构造一个类型的实例(通过调用一个构造器方法)、获取/设置一个字段或获取/设置一个属性。为了告诉InvokeMember应采取哪个行动,请指定下面列出的一个BindingFlags。

 

列出的大多数标志都是互斥-调用InvokeMember时,要选择、而且只能选择其中一个。但是,可同时指定GetField和GetProperty。在这种情况下,InvokeMember先查询一个匹配的字段,如果没有找到一个匹配的字段,就查找一个匹配的属性。类似的,可以同时指定SetField和SetProperty,他们按相同的方式匹配。绑定器利用这些标识来缩小匹配的范围。如果指定了BindingFlags.CreateInstance标志,绑定器就知道它只能选择一个构造方法。

 

 

InvokeMethod       告诉InvokeMember调用一个方法。

 

CreateInstance       告诉InvokeMember创建一个新对象并调用其构造器

 

GetField      告诉InvokeMember获取一个字段的值

 

SetField       告诉InvokeMember设置一个字段的值

 

GetProperty      告诉InvokeMember调用一个属性的get访问器方法

 

SetProperty       告诉InvokeMember调用一个属性的set访问器方法

 

 

5、一次绑定,多次调用

 

利用Type的InvokeMember方法,可以访问一个类型的所有成员。但是,应该注意的是,每次调用InvokeMember方法时,它都必须先绑定到一个特定的成员。然后才能调用。如果每次调用一个成员时都要让绑定器选择恰当的成员,那么将是一个非常耗时的工作。如果频繁的进行这样的操作,应用程序的性能势必受到一定影响。因此,如果打算频繁访问一些成员,最好是一次绑定,多次调用。

 

本章已经讨论过了如何调用Type的某个方法来绑定成员:GetFields,GetConstructors,GetMethods,GetProperties,GetEvents或者其他任何类似的方法。所有的这些方法返回对一个对象的引用,该对象的类型提供了直接访问特定成员的方法。

PropertyInfo类型代表与属性有关的元数据信息;也就是说,PropertyInfo提供了CanRead,CanWrite和PropertyType只读属性。这些属性指出属性时可读的还是可写的,以及属性的数据类型时什么。PropertyInfo有一个GetAccessors方法,它能返回由MethodInfo元素构成的数据:一个元素的get访问器访问方法,另一个元素时set访问器方法。PropertyInfo提供了更有价值的方法是GetGetMethod和GetSetMethod,这两个方法都返回MethodInfo对象。PropertyInfo的GetValue和SetValue方法只是为了提供方便;在内部,他们会获取恰当的MethodInfo并调用它。为了支持有参属性,GetValue和SetValue方法提供了一个Object[]类型的index参数。

 

FieldInfo       调用GetValue获取字段的值 ,调用SetValue设置字段的值。

 

ConstructorInfo  调用Invoke构造类型的一个实例,并调用类型构造器。

 

MethodInfo      调用Invoke调用一个类的方法

 

PropertyInfo       调用GetValue调用属性的get访问器,调用SetValue调用属性的set访问器。

 

EventInfo       调用AddEventHandle调用事件的add访问器方法,调用RemoveEventHandle调用事件的remove访问器方法。

 

EventInfo       类型代表与事件有关的元数据信息。EventInfo类型提供了一个EventHandlerType只读属性,该属性返回事件的基础委托的Type。EventInfo类还提供了GetAddMethod和GetRemoveMethod方法,他们都返回一个MethodInfo。这个MethodInfo对应于为事件添加或删除委托方法。要添加或删除一个委托,要添加或删除一个委托,可调用这些MethodInfo对象,也可调用EventInfo类型提供更好用的AddEventHandler和RemoveEventHandler方法。

 

 

调用上面右侧的某个方法时,不是绑定成员,而是直接调用成员。可多次调用任何一个这样的方法。由于不需要绑定,所以性能也比较好。

 

注意,ConstructorInfo的Invoke方法、MethodInfo的Invoke方法、  PropertyInfo       的SetValue/GetValue方法都有一个重载版本要获取一个Binder派生对象的引用以及一些BindingFlags。这可能会误以为这些方法要绑定到成员,但事实并不是这样。

 

调用这些方法时,要在Binder派生对象的帮助下,对提供的方法参数进行类型转换,例如将一个Int32实参转换为Int64,使已选中的方法可以正常调用。至于BindingFlags参数,这里唯一可以传递的标志就是BindingFlags. SuppressChangeType.但是绑定器完全可以忽略这个标志。幸好,DefaultBinder类会注意到这个标志。DefaultBinder看到这个标志使,它不会转换任何实参。如果使用了这个标志,而且传入的实参和方法期望的实参不匹配,就会抛出异常。

 

通常,用BindingFlags.ExactBinding标志绑定一个成员后,要指定BindingFlags. SuppressChangeType标志来调用成员。如果没有以前以后来使用这两个标志,成员调用很有可能失败,除非传递的实参恰好就是方法所需要的。顺便说一句,调用MemberInfo的InvokeMethod来绑定并调用一个成员时,上诉两个标志可能都要指定或者都不指定。

 

下面应用程序演示了使用反射来访问类型成员的各种方式。SomeType类代表有多种成员的一个类型。这些成员包含:一个私有字段;一个公共构造器,它获取一个传引用的Int32实参;一个公共方法;一个公共属性;以及一个公共事件。定义好了SomeType类型之后,我提供了三个不同的方法,他们利用反射来访问SomeType的成员。每个方法都用不同的方式来做相同的事情。

 

UserInvokeMemberToBindAndInvokeTheMember  方法演示了如何利用Type的InvokeMember来绑定一个成员。

 

BindToMemberThenInvokeTheMember  方法演示了如何绑定一个成员,并在以后调用它。如果你打算在不同的对象上多次调用一个成员,这个技术可能产生性能更好的代码。

 

BindToMemberCreateDeleteToMemberThenInvokeTheMember  方法演示了如何绑定到一个对象或成员,然后创建一个委托来引用该对象或成员。通过委托来调用速度非常快。如果在相同的对象上多次调用相同的成员,这个技术性能上产生比上一个技术还要快的代码。

 

UseDynamicToBindAndInvokeTheMember  方法演示了如何使用C#的dynamic基础类型来简化访问成员时使用的语法。除此之外,如果打算在相同类型的不同对象上调用相同的成员,这个技术还能提供不错的性能,因为针对每个类型,绑定都只会发生一次。而且可以缓存起来,以后多次调用时非常快。还可以用这个技术调用不同类型的对象的成员。

 

 private static void UserInvokeMemberToBindAndInvokeTheMember(Type t)
        {
            Console.WriteLine("UserInvokeMemberToBindAndInvokeTheMember");

            //构造Type的一个实例
            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 called:" + args[0]);
            //读写一个字段
            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("m_someField:" + v);

            //调用一个方法
            string s = (string)t.InvokeMember("ToString", c_bf | BindingFlags.InvokeMethod, null, obj, null);

            Console.WriteLine("ToString:" + s);

            //读写一个属性
            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/remove方法,为事件添加和删除一个委托

            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 static void BindToMemberThenInvokeTheMember(Type t)
        {
            Console.WriteLine("BindToMemberThenInvokeTheMember");
            //构造一个实例
            ConstructorInfo ctor = t.GetConstructor(new Type[] { Type.GetType("System.Int32&") });
            //上面这一行也可以向下面这样写
            // t.GetConstructor(new Type[] { 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 return: " + args[0]);
            //读写一个字段
            FieldInfo fi = obj.GetType().GetField("m_someField", c_bf);
            fi.SetValue(obj, 33);
            Console.WriteLine("someField" + fi.GetValue(obj));

            //调用一个方法

            MethodInfo mi = obj.GetType().GetMethod("ToString", c_bf);
            String s = (String)mi.Invoke(obj, null);
            Console.WriteLine("ToString" + s);
            //读写一个属性

            PropertyInfo pi = obj.GetType().GetProperty("SomeProp", typeof(Int32));
            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);
            Console.WriteLine("SomeProp:" + pi.GetValue(obj, null));

            EventInfo ei = obj.GetType().GetEvent("SomeEvent", c_bf);
            EventHandler ts = new EventHandler(EventCallback);
            ei.AddEventHandler(obj, ts);
            ei.RemoveEventHandler(obj, ts);
        }

  

 public static void EventCallback(object o, EventArgs e) { }

        private static void BindToMemberCreateDeleteToMemberThenInvokeTheMember(Type t)
        {
            Console.WriteLine("BindToMemberCreateDeleteToMemberThenInvokeTheMember");
            //构造一个类的实例
            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 return: " + args[0]);
            //调用一个方法
            MethodInfo mi = obj.GetType().GetMethod("ToString", c_bf);
            var toString = (Func<String>)Delegate.CreateDelegate(typeof(Func<String>), obj, mi);
            String s = toString();
            Console.WriteLine("ToString" + s);

            //读写一个属性
            PropertyInfo pi = obj.GetType().GetProperty("SomeProp", typeof(Int32));
            var setSomeProp = (Action<Int32>)Delegate.CreateDelegate(typeof(Action<Int32>), obj, pi.GetSetMethod());
            try
            {
                setSomeProp(0);
            }
            catch (ArgumentOutOfRangeException)
            {
                Console.WriteLine("Property set catch.");
            }
            setSomeProp(2);

            var getGetMethod = (Func<Int32>)Delegate.CreateDelegate(typeof(Func<Int32>), obj, pi.GetGetMethod());
            Console.WriteLine("SomeProp:" + getGetMethod());

            //从事件中添加和删除一个委托
            EventInfo ei = obj.GetType().GetEvent("SomeEvent", c_bf);
            var addSomeEvent = (Action<EventHandler>)Delegate.CreateDelegate(typeof(Action<EventHandler>), obj, ei.GetAddMethod());
            addSomeEvent(EventCallback);
            var removeSomeEvent = (Action<EventHandler>)Delegate.CreateDelegate(typeof(Action<EventHandler>), obj, ei.GetRemoveMethod());
            removeSomeEvent(EventCallback);
        }

  

 private static void UseDynamicToBindAndInvokeTheMember(Type t)
        {
            Console.WriteLine("UseDynamicToBindAndInvokeTheMember");
            //构造一个实例
            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 return: " + args[0]);
            try
            {
                obj.m_someField = 5;
                Int32 v = (Int32)obj.m_someField;
                Console.WriteLine("someField:" + v);
            }
            catch (RuntimeBinderException e)
            {
                Console.WriteLine("Failed to access field:" + e.Message);
            }
            //调用一个方法
            String s = (String)obj.ToString();
            Console.WriteLine("ToString:" + s);
            //读写一个属性
            try
            {
                obj.SomeProp = 0;
            }
            catch (ArgumentOutOfRangeException)
            {
                Console.WriteLine("Property set catch.");
            }
            obj.SomeProp = 2;
            Int32 val = (Int32)obj.SomeProp;

            Console.WriteLine("SomeProp" + val);
            obj.SomeEvent += new EventHandler(EventCallback);
            obj.SomeEvent -= new EventHandler(EventCallback);
        }

  

6.使用绑定句柄来减少进程的内存耗用

 

许多应用程序绑定一组类型(Type对象)或者类型成员(从MemberInfo派生的对象),并将这些对象保存在某种形式的一个集合中。以后,应用程序会搜索这个集合,查找特定的对象,然后调用这个对象。这是一个很好的机制,只是有一个小问题:Type和MemberInfo派生对象需要大量内存。因此,如果一个应用程序容纳了太多这样的对象,但只是偶尔调用一下他们,应用程序的内存耗用就会急剧增长,对应用程序的性能产生负面影响。

 

在内部,CLR用一种更精简的方式标识这种信息。CLR之所以为应用程序创建这些对象,只是为了简化开发人员的工作。CLR在运行时本身不需要这些大对象。如果需要保存和缓存大量的Type和MemberInfo派生对象,开发人员可以使用运行时句柄来代替对象,从而减少工作集(占用的内存)。FCL定义了三个运行时句柄类型:RuntimeTypeHandle,RuntimeFieldHandle和RuntimeMethodHandle。三个类型都是值类型,他们只包含一个字段,也就是一个IntPrt;这样一来,这些类型的实例显得相当精简。IntPrt字段时一个句柄,它引用了AppDomain的Loader堆中的一个类型、字段或方法。因此,现在需要一一种简单,高效的方式将一个重量级的Type或MemberInfo对象转化为一个轻量级的运行时句柄实例。反之依然。幸好,使用以下方法和属性,可以轻松达到上述目的。

 

  1. 要将一个Type对象转化为一个RuntimeTypeHandle,调用Type的静态方法GetTypeHandle,并传递那个Type对象引用。
  2. 要将一个RuntimeTypeHandle转换为一个Type对象,调用Type的静态方法GetTypeFromHandle,并传递那个RuntimeHandle。
  3. 要将一个FieldInfo对象转换为一个RuntimeFieldHandle,查询FieldInfo的实例只读属性FieldHandle。
  4. 要将一个RuntimeFieldHandle转换为一个FieldInfo对象,调用FieldInfo的静态方法GetFieldFromHandle.
  5. 要将一个MethodInfo对象转换为一个RuntimeMethodHandle,查询MethodInfo的实力只读属性MethodHandle.
  6. 要将一个RuntimeMethodHandle转化为一个MethodInfo对象,调用MethodInfo静态方法GetMethodFromHandle.

 

private const BindingFlags c_bf = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
        static void Main(string[] args)
        {
            Show("before doing anything");
            List<MethodBase> methodInfos = new List<MethodBase>();

            foreach (Type t in typeof(Object).Assembly.GetExportedTypes())
            {
                if (t.IsGenericTypeDefinition) continue;

                MethodBase[] mb = t.GetMethods(c_bf);
                methodInfos.AddRange(mb);
            }
            Console.WriteLine("# of methods={0:###,###}", methodInfos.Count);
            List<RuntimeMethodHandle> methodHandle = methodInfos.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle);
            Show("Holding MethodInfo and RuntimemethodHandle cache");
            GC.KeepAlive(methodInfos);
            methodInfos = null;
            Show("After freeing MethodInfo objects");
            methodInfos = methodHandle.ConvertAll<MethodBase>(mb => MethodBase.GetMethodFromHandle(mb));
            Show("Size of heap after re-creating methodinfo objects");

            GC.KeepAlive(methodInfos);
            GC.KeepAlive(methodHandle);

            methodHandle = null;
            methodInfos = null;
            Show("After freeing MethodInfos and RuntimeMethodHandle");
        }

  

 

   

  

posted @ 2015-06-14 23:35  -祐扌戒恉-  阅读(294)  评论(0编辑  收藏  举报