反射和元数据

1. 概述

        C#程序可以编译为一个包含元数据、编译代码和资源的程序集。在运行时检查并使用元数据和编译代码的操作称为反射

        反射相关的API均位于System.Reflection命名空间下。我们甚至可以通过System.Reflection.Emit命名空间中的类在运行时动态创建新的元数据和可执行IL(中间语言)指令。

2. 反射和激活类型

2.1. 获取类型

        System.Type的实例代表了类型的元数据。可以说,System.Type实例就是打开类型(和定义该类型的程序集)的所有元数据的窗口。System.Type是一个抽象类型。因此,实际上typeof运算符获得的是一个Type的子类。这种CLR使用的子类属于mscorlib的内部类型,称为RuntimeType。

        调用任意对象的GetType方法或者使用C#的typeof运算符都可以得到System.Type的实例

还可以通过类型的名称获得Type对象。如果我们拥有类型所在的Assembly引用,可以调用Assembly.GetType方法。

        如果我们并没有Assembly对象,则可以通过对象的程序集限定名称(类型的完整名称加上程序集的完全限定名称)获得对象的实例。同时,该程序集会隐式加载(就像是调用了Assembly.Load(string)方法一样)。

         Type具有AsAssemblyQualifiedName属性,其值是类型的FullName,随后跟一个逗号,之后是程序集的完整名称。

2.1.1. 获取数组类型

        typeof运算符和GetType同样适用于数组类型。

        还可以对元素类型调用MakeArrayType以获得数组类型。

        MakeArrayType方法可以接受一个int类型的参数以创建多维矩形数组。

        getElementType方法可以返回数组元素的类型。GetArrayRank则可以返回矩形数组的维数。

        

2.1.2. 获取嵌套类型

        要获得嵌套类型,可以在包含类型上调用GetNestedTypes方法。

        其中的+会将包含类型与嵌套的命名空间分隔开来。

        

2.2. 类型名称

        类型具有Namespace、Name以及FullName属性。在大多数情况下,FullName是前两者的组合。嵌套类型和封闭的泛型类型例外。

2.2.1. 嵌套类型名称

        对于嵌套类型来说,其FullName仅仅是包含的类型名称。其中的+会将包含类型与嵌套的命名空间分隔开来。

2.2.2. 泛型类型名称

        泛型类型名称带有'后缀,后续加上类型参数的数目。如果泛型类型是未绑定的类型,则其Name和FullName都将遵循该规则。

        如果泛型类型是封闭的,则其FullName(而且仅有FullName)会包含额外的附加信息。其中,每一个类型参数枚举都将使用其程序集限定名称。

2.2.3. 数组和指针类型名称

        数组类型的名称和typeof表达式使用的后缀是相同的

2.2.4. ref和out参数的类型名称

        ref和out类型的参数带有&后缀

2.3. 基本类型和接口

        Type类中具有BaseType属性;

        GetInterfaces方法会返回类型实现的接口

2.4. 实例化类型

        从类型创建对象实例的方式有两种:

        1)调用静态的Activator.CreateInstance方法

        2)调用ConstructorInfo.Invoke方法,并使用Type的GetConstructor方法的返回值作为参数

        Activator.CreateInstance接受Type类型的参数,并且还可以接受可选的参数作为传递给构造器的参数

        CreateInstance方法可以指定很多选项,例如该类型所在的程序集、目标应用程序域以及是否绑定非公有的构造器。如果运行时无法找到合适的构造器,则会抛出Missing-MethodException。
        当所用参数值无法甄别重载构造器时,就需要调用ConstructorInfo的Invoke方法。例如,假设X类拥有两个构造器:一个构造器接受string参数;另一个接受StringBuilder类型的参数。如果向Activator.CreateInstance传递null参数,则调用目标就具有二义性。此时就需要使用ConstructorInfo

2.5 泛型类型

        Type既可以表示封闭的泛型类型也可以表示未绑定类型参数的泛型类型。在编译时只能够实例化封闭的泛型类型,而无法实例化未绑定的泛型类型

        MakeGenericType方法接受类型参数,即可将未绑定的泛型类型转换为封闭的泛型类型。而GetGenericTypeDefinition方法则实现相反的操作

        GetGenericArguments可以返回封闭泛型类型的类型参数;对于未绑定的泛型类型,GetGenericArguments会返回在泛型类型定义中指定为占位符类型的伪类型

3.反射并调用成员

        GetMembers方法可以返回类型的成员。如果在调用GetMembers方法时不传递任何参数,则该方法会返回当前类型(及其基类)的所有公有成员。GetMember方法则可以通过名称检索特定的成员。由于成员可能会被重载,因此该方法仍然会返回一个数组。当调用GetMembers方法时,可以传递一个MemberTypes实例来限定返回的成员类型。此外,还可以调用GetMethods、GetFields、GetProperties、GetEvents、GetConstructors以及GetNestedTypes方法获得相应的成员。以上方法均有返回指定的单个成员的版本。

3.1 成员类型

        可以根据MemberInfo的MemberType属性将MemberInfo转换为相应的子类型。如果通过GetMethod、GetField、GetProperty、GetEvent、GetConstructor或者GetNestedTypes(及其复数版本)获取成员则无须进行转换。

        所有的*Info实例都会在第一次使用时由反射API缓存,和保存对象标识的作用一样,这种缓存有助于改善慢速API的性能。

3.2 泛型类型成员

        我们不但可以从未绑定的泛型类型中获得成员元数据,也可以从封闭的泛型类型中获得这些数据:

using System;
using System.Collections.Generic;
using System.Reflection;

namespace helloworld
{
    class Program
    {
        
        static void Main(string[] args)
        {
            PropertyInfo[] propertyInfos = typeof(IEnumerator<>).GetProperties();
            foreach (var property in propertyInfos)
            {
                Console.WriteLine(property.DeclaringType);
            }
            PropertyInfo unbound = typeof(IEnumerator<>).GetProperty("Current");
            PropertyInfo closed = typeof(IEnumerator<int>).GetProperty("Current");

            Console.WriteLine(unbound);   
            Console.WriteLine(closed);   

            Console.WriteLine(unbound.PropertyType.IsGenericParameter); 
            Console.WriteLine(closed.PropertyType.IsGenericParameter);   
            Console.ReadLine();
        }
    }
}

         从未绑定的或者封闭的泛型类型中得到的MemberInfo是相互独立的,即使对于签名中不含有泛型类型参数的成员也是如此

3.3 动态调用成员

        一旦得到了MethodInfo、PropertyInfo或者FieldInfo对象,我们就可以动态对其进行调用或者得到并设置它们的值。这称为“动态绑定”或者“后期绑定”。它会在运行时(而不是在编译时)来决定成员的调用。

static void Main(string[] args)
        {
            string s = "Hello";
            int length = s.Length;


            object s1 = "Hello";
            PropertyInfo prop = s1.GetType().GetProperty("Length");
            int length1 = (int)prop.GetValue(s1, null);               // 5
        }

3.4 方法的参数

using System;
using System.Reflection;

namespace helloworld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("方法的参数");
            Type type = typeof(string);
            Type[] types = { typeof(int), typeof(int) };
            //由于Substring有重载方法,因此我们必须将一组参数的类型传递到GetMethod中以获得所需要的方法
            //MethodBase(MethodInfo和ConstructorInfo的基类)的GetParameters方法将会返回方法参数的元信息
            MethodInfo methodInfo = type.GetMethod("Substring", types);
            object[] ars = {2,4};
            object rv = methodInfo.Invoke("xsxddshj", ars);
            Console.WriteLine(rv);
            //Console.ReadKey();
            MethodInfo[] methodInfos = type.GetMethods();
            foreach (var m in methodInfos)
            {
                Console.WriteLine(m.Name + " " + m.GetParameters() + " " + m.GetGenericArguments().Length);
            }
            
        }
    }
}

3.5 使用委托提高性能

        动态调用的效率不高,其开销通常为几微秒。如果要在一个循环中重复调用某个方法,则可以为目标动态方法动态实例化一个委托,这样就可以将微秒级的开销降低到纳秒级。

class Program
    {
        //定义了一个委托
        delegate string StringToString(string s,char c);

        static void Main(string[] args)
        {
            MethodInfo trimMethod = typeof(string).GetMethod("Trim", new Type[1] { typeof(char)});
            var trim = (StringToString)Delegate.CreateDelegate
                                    (typeof(StringToString), trimMethod);
            for (int i = 0; i < 1000000; i++)
            {
                //执行很快,这是因为动态绑定仅仅执行了一次
                Console.WriteLine(trim("test", 't'));
            }
           
        }
    }

3.6 访问非公有成员

        类型上所有检测元数据的方法(例如GetProperty、GetField等)都含有使用BindingFlags枚举的重载方法。

        BindingFlags可以按位组合。

using System;
using System.Reflection;

namespace helloworld
{
    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(string);
           
            MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Static|BindingFlags.Public);
            foreach (var m in methodInfos)
            {
                Console.WriteLine(m.Name + " " + m.GetParameters() + " " + m.GetGenericArguments().Length);
            }
            
        }
    }
}

3.7 泛型方法

        泛型方法无法直接调用,调用泛型方法需要一个额外的步骤,即在MethodInfo对象上调用MakeGeneric-Method方法来指定确切的泛型类型参数。该方法将返回一个新的MethodInfo

using System;
using System.Reflection;

namespace helloworld
{
    class Program
    {
        public static T Test<T>(T x) { return x; }
        static void Main(string[] args)
        {
            MethodInfo methodInfo = typeof(Program).GetMethod("Test");
            MethodInfo intTest = methodInfo.MakeGenericMethod(typeof(int));
        }
    }
}

3.8 调用未知类型的泛型接口成员

        当需要调用(直到运行时才能得知的)未知类型参数的泛型接口成员时,使用反射是非常有效的。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace helloworld
{
    class Program
    {
        public static T Test<T>(T x) { return x; }
        static void Main(string[] args)
        {
            Console.WriteLine(ToStringEx(new List<int> { 5, 6, 7 }));
            Console.WriteLine(ToStringEx("xyyzzz".GroupBy(c => c)));
        }
        //public static string ToStringEx<T>(IEnumerable<T> sequence)
        //{
        //    return "";
        //}
        //这种声明有很多限制,如果sequence中包含需要枚举的嵌套集合该怎么办呢?我们只能重载该方法
        //public static string ToStringEx<T>(IEnumerable<IEnumerable<T>> sequence)
        //{
        //    return "";
        //}
        //那么当sequence含有分组或者嵌套序列的映射时,方法重载的静态解决方案也就变得不太可行了。我们需要一个既可以扩展又能处理任何对象图的方式
        //,这段代码是无法编译的:因为未绑定泛型类型参数的类(例如List<>或者IGrouping<>)的成员是无法调用的。本例中的未绑定类型参数的类为List<>。
        //我们可以通过使用非泛型的IList接口来解决这个问题
        //public static string ToStringEx(object value)
        //{
        //    if (value == null) return "<null>";
        //    StringBuilder sb = new StringBuilder();

        //    if (value is List<>)                                            // Error
        //        sb.Append("List of " + ((List<>)value).Count + " items");   // Error

        //    if (value is IGrouping <,>)                                      // Error
        //        sb.Append("Group with key=" + ((IGrouping <,>) value).Key);   // Error

        //    // Enumerate collection elements if this is a collection,
        //    // recursively calling ToStringEx()
        //    // ...

        //    return sb.ToString();
        //}
        //public static string ToStringEx(object value)
        //{
        //    if (value == null) return "<null>";
        //    StringBuilder sb = new StringBuilder();

        //    if (value is IList)                                            // Error
        //        sb.Append("List of " + ((IList)value).Count + " items");   // Error
        //    //可惜的是对于IGrouping<,>来说上述方案就无法达成了
        //    //if (value is IGrouping <,>)                                      // Error
        //    //    sb.Append("Group with key=" + ((IGrouping <,>) value).Key);   // Error

        //    // Enumerate collection elements if this is a collection,
        //    // recursively calling ToStringEx()
        //    // ...

        //    return sb.ToString();
        //}
        //由于没有访问Key属性的非泛型类型,因此我们只能使用反射方式。
        //这个解决方案并非要调用未绑定泛型类型的成员(当然也是不可能的),
        //而是在运行时确定参数类型,并调用封闭参数类型的泛型类型成员。

        //首先需要确定value是否实现了IGrouping<,>,如果答案是肯定的,
        //则需要封闭泛型接口的类型参数。使用LINQ查询是解决这个问题的良好手段。
        //此后,我们将获得并调用Key属性


        public static string ToStringEx(object value)
        {
            if (value == null) return "<null>";
            if (value.GetType().IsPrimitive) return value.ToString();

            StringBuilder sb = new StringBuilder();

            if (value is IList)
                sb.Append("List of " + ((IList)value).Count + " items: ");

            Type closedIGrouping = value.GetType().GetInterfaces()
              .Where(t => t.IsGenericType &&
                          t.GetGenericTypeDefinition() == typeof(IGrouping<,>))
              .FirstOrDefault();

            if (closedIGrouping != null)   // Call the Key property on IGrouping<,>
            {
                PropertyInfo pi = closedIGrouping.GetProperty("Key");
                object key = pi.GetValue(value, null);
                sb.Append("Group with key=" + key + ": ");
            }

            if (value is IEnumerable)
                foreach (object element in ((IEnumerable)value))
                    sb.Append(ToStringEx(element) + " ");

            if (sb.Length == 0) sb.Append(value.ToString());

            return "\r\n" + sb.ToString();
        }
    }
}

4. 反射程序集

        若需要动态反射程序集,只需调用Assembly对象的GetType或者GetTypes即可

using NPOI.SS.Formula.Functions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            //获取包含当前执行的代码的程序集
            Type type = Assembly.GetExecutingAssembly().GetType("ConsoleApp1.Program");
            Console.WriteLine(type.Name);
            foreach (var method in type.GetMethods())
            {
                Console.WriteLine(method.Name+" :"+method.IsStatic);
            }
        }
        public static string ToStringEx(object value)
        {
            if (value == null) return "<null>";
            //Type.IsPrimitive获取一个值,通过该值指示 Type 是否为基元类型之一。
            if (value.GetType().IsPrimitive) return value.ToString();

            StringBuilder sb = new StringBuilder();

            if (value is IList)
                sb.Append("List of " + ((IList)value).Count + " items: ");

            Type closedIGrouping = value.GetType().GetInterfaces()
              .Where(t => t.IsGenericType &&
                          t.GetGenericTypeDefinition() == typeof(IGrouping<,>)).FirstOrDefault();
            if(closedIGrouping != null)   // Call the Key property on IGrouping<,>
            {
                PropertyInfo pi = closedIGrouping.GetProperty("Key");
                object key = pi.GetValue(value, null);
                sb.Append("Group with key=" + key + ": ");
            }

            if (value is IEnumerable)
                foreach (object element in ((IEnumerable)value))
                    sb.Append(ToStringEx(element) + " ");

            if (sb.Length == 0) sb.Append(value.ToString());

            return "\r\n" + sb.ToString();

        }
    }
}

5.使用特性

        CLR允许使用特性将额外的元数据追加到类型、成员和程序集上。

5.1 特性基础

C#的三类特性
名称概述备注
位映射特性位映射特性(本书中定义的术语)可以映射到类型元数据的特定位上。大多数的C#修饰符关键字都会编译为位映射特性不可扩展
自定义特性自定义特性可以编译为类型的主元数据表中的二进制数据。所有的自定义特性都是由System.Attribute的子类表示的.这些元数据中的二进制块就是该特性类的标识,其中还包含了所有占位和命名的参数的值。可扩展
伪自定义特性伪自定义特性与自定义特性之间的差异在于编译器或者CLR内部会进行优化,将伪自定义特性转换为位映射特性。不可扩展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值