反射

元数据

元数据是用一系列表来存储的,生成一个程序集或模块时,编译器会创建一个类型定义表、一个字段定义表、一个方法定义表以及其它表。

反射

程序运行的时候解析这些元数据表以获取信息,该行为便是反射。反射允许在运行时发现并使用编译时还不了解的类型及其成员。

反射的性能
  • 反射会造成编译时无法保证类型安全性。反射需要重度依赖字符串,所以会丧失编译时的类型安全。例如执行Type.GetType("A"),在一个程序集中查找类型名为“A”的类型,但程序集实际包含的类型可能是“AA”,代码会通过编译,但运行时会出错。
  • 反射速度慢,使用反射时,类型及其成员的名称在编译时未知,要使用字符串名称标识每个类型及其成员,以便在运行时发现它们。也就是说在扫描程序集的元数据时,反射要不断的执行字符串的搜索,而字符串的搜索执行的是不区分大小写的比较,这会进一步影响速度。
反射调用一个成员时也会对性能产生影响

反射调用方法时,首先必须将实参打包成一个数组,在内部反射必须将这些实参解包到线程栈上。此外在调用方法前,CLR必须检查实参具有正确的数据类型,最后CLR还需确保调用者有正确的安全权限来访问被调用的成员。

*综上所述,最好避免使用反射技术来访问字段或者调用方法。

获取Type对象的几种方式
  • Type的静态方法GetType:接收一个string参数,必须指定类型的全名(包括命名空间),如果调用程序集没有定义指定的类型,就查找MSCorLib.dll定义的类型,如果还是没找到就返回null
  • Type的静态方法ReflectionOnlyGetType:行为与GetType相似,只是类型会加载到一个“仅反射”上下文中,不能执行
构造类型的实例
  • Activator的静态方法CreateInstance:调用该方法可以传递一个Type对象引用,也可以传递标识了想要创建的类型的一个String。其中直接获取一个类型对象的几个重载版本相对简单,为类型的构造器传递一组实参,方法返回的是对新对象的一个引用。而使用字符串来指定所需类型的几个重载版本要稍微复杂一些,首先必须指定另一个字符串来标识定义了类型的那个程序集。其次这些版本返回的不是对新对象的引用,而是一个System.Runtime.Remoting.ObjectHandle对象。ObjectHandle类型允许将一个AppDomain中创建的对象传至其它AppDomain,期间不强迫对象具体化,要具体化该对象可以调用ObjectHandle对象的Unwrap方法。在一个AppDomain中调用这个方法时,它会将定义了要具体化的类型的程序集加载到这个AppDomain中。如果对象按引用封送就创建代理类型和对象。如果按值封送,对象的副本会被反序列化。
  • Activator的静态方法CreateInstanceFrom:行为与CreateInstance方法相似,不同的是必须通过字符串来指定类型及其程序集。程序集要使用Assembly的LoadFrom(而非Load)方法加载到调用的AppDomain中。由于没有接收Type参数的版本,所以返回的都是ObjectHandle对象的引用,必须调用ObjectHandle的Unwrap方法进行具体化
构造泛型类型的实例
static void Main()
{
    //获取泛型类型的类型对象的一个引用
    Type temp = typeof(List<>);

    //使用int类型封闭泛型类型
    Type closedType = temp.MakeGenericType(typeof(int));

    //构造封闭类型的实例
    object o = Activator.CreateInstance(closedType);

    Console.WriteLine(o.GetType());
}

运行结果

发现类型成员

字段、构造器、方法、属性、事件和嵌套类型都可以被定义为类型的成员。FCL包含一个System.Reflection.MemberInfo的类型,封装了一组所有类型都通用的属性。层次结构图如下:

查询一个类型的成员并显示与之相关的一些信息
static void Main()
{
    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
    //遍历AppDomain中加载的所有程序集
    foreach (var item in assemblies)
    {
        Console.WriteLine("Assembly: {0}", item);

        //查找程序集中的类型
        foreach (var t in item.GetExportedTypes())
        {
            Console.WriteLine("Type: {0}", t);

            //发现类型成员
            const BindingFlags bf = BindingFlags.DeclaredOnly |
                BindingFlags.NonPublic | BindingFlags.Public |
                BindingFlags.Instance | BindingFlags.Static;

            foreach (var mi in t.GetMembers(bf))
            {
                var typeName = string.Empty;
                if (mi is Type)
                    typeName = "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";

                Console.WriteLine("mi typeName: {0}", typeName);
            }
        }
    }
}
BindingFlags枚举(筛选返回的成员类型)
  • Default:指定未定义任何绑定标志
  • IgnoreCase:返回与指定字符串匹配的成员(忽略大小写)
  • DeclaredOnly:只返回被反射的那个类型的成员,忽略继承的成员
  • Instance:返回实例成员
  • Static:返回静态成员
  • Public:返回公共成员
  • NonPublic:返回非公共成员
  • FlattenHierarchy:返回基类型定义的公共成员和受保护静态成员

遍历反射对象模型图

基于一个AppDomain可发现加载到其中的所有程序集。基于一个程序集可发现构成它的所有模块。基于一个程序集或模块可发现它定义的所有类型。基于一个类型可发现它的嵌套类型、字段、构造器、方法、属性和事件

发现类型的接口
interface ITest1 : IDisposable
{
    void Method1();
    void Method2();
}

interface ITest2
{
    void Method1();
}

class MyClass : ITest1, ITest2, IDisposable
{
    //ITest1
    void ITest1.Method1() { }

    public void Method2() { }

    //ITest2
    void ITest2.Method1() { }

    //IDisposable
    public void Dispose() { }

    //非接口方法
    public void Method1() { }
}

class Program
{
    static void Main()
    {
        //查找MyClass实现的接口
        Type t = typeof(MyClass);
        Type[] interfaces = t.FindInterfaces(TypeFilter, typeof(Program).Assembly);

        //显示每个接口的信息
        foreach (var item in interfaces)
        {
            Console.WriteLine("接口: {0}", item);

            //获取接口映射
            InterfaceMapping map = t.GetInterfaceMap(item);

            for (int i = 0; i < map.InterfaceMethods.Length; i++)
            {
                Console.WriteLine("方法 {0} 在 {1} 中实现", map.InterfaceMethods[i], map.TargetMethods[i]);
            }
        }

        Console.ReadKey();
    }

    private static bool TypeFilter(Type t, object filterCriteria)
    {
        //如果接口是由filterCriteria标识的程序集中定义的就返回true
        return t.Assembly == (Assembly)filterCriteria;
    }
}

结果

调用类型的成员

Type类提供了一个InvokeMember方法,可以通过这个方法调用一个类型的成员。在调用这个方法时,会在类型的成员中搜索一个匹配的成员,如果没有找到则会抛出一个异常。反之则会调用成员,该成员返回什么InvokeMember方法也会返回一个同样的信息数据。

InvokeMember使用的BindingFlags

一次绑定多次调用

使用InvokeMember方法的弊端在于每次调用该方法,都必须先绑定到一个特定的成员然后才能调用,这大大地增加了耗时。可以直接调用Type的某个方法来绑定成员(GetFields,GetMethods等),可以返回对一个对象的引用,通过对象引用直接访问特定成员。

示例代码

class SomeType
{
    private int m_SomeValue;
    public int SomeValue
    {
        get
        {
            return this.m_SomeValue;
        }

        set
        {
            this.m_SomeValue = value;
        }
    }

    public string SomeMethod()
    {
        Console.WriteLine("Run SomeMethod");
        return "SomeMethod Rusult";
    }
}

static void Main()
{
    var bf = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
    Type t = typeof(SomeType);
    //构造Type实例
    object o = t.InvokeMember(null, bf | BindingFlags.CreateInstance, null, null, null);
    Console.WriteLine("o type : {0}", o.GetType().ToString());

    //读写字段
    t.InvokeMember("m_SomeValue", bf | BindingFlags.SetField, null, o, new object[] { 1 });
    int v = (int)t.InvokeMember("m_SomeValue", bf | BindingFlags.GetField, null, o, null);
    Console.WriteLine("m_SomeValue = {0}", v);

    //调用一个方法
    string methodRes = (string)t.InvokeMember("SomeMethod", bf | BindingFlags.InvokeMethod, null, o, null);
    Console.WriteLine("methodRes = {0}", methodRes);

    Console.WriteLine("\n---------分割线---------\n");

    //构造实例
    ConstructorInfo ctor = t.GetConstructor(new Type[] { });
    o = ctor.Invoke(null);

    //读写字段
    FieldInfo fi = o.GetType().GetField("m_SomeValue", bf);
    fi.SetValue(o, 2);
    Console.WriteLine("m_SomeValue = {0}", fi.GetValue(o));

    //调用方法
    MethodInfo mi = o.GetType().GetMethod("SomeMethod", bf);
    methodRes = (string)mi.Invoke(o, null);
    Console.WriteLine("methodRes = {0}", methodRes);

    Console.ReadKey();
}

结果

使用句柄减少进程中内存的消耗

由于Type和MemberInfo的派生对象需要大量内存,因此如果应用程序容纳了太多这样的对象,但只是偶尔调用一下,内存消耗将急剧增长,对性能产生负面影响。

解决方法:如果需要大量缓存Type和MemberInfo的派生对象,我们可以使用允许时句柄来替代对象,从而减小工作集。FCL定义了三个运行时句柄类型,分别是:RuntimeTypeHandle,RuntimeFieldHandle,RuntimeMethodHandle,三个类型均属于值类型,只包含一个IntPtr字段,这个字段是一个句柄,引用了AppDomain的Loader堆中的一个类型、字段或方法。

转化方法:

  • Type转RuntimeTypeHandle:调用Type的静态方法GetTypeHandle
  • RuntimeTypeHandle转Type:调用Type的静态方法GetTypeFromHandle
  • FieldInfo转RuntimeFieldHandle:查询FieldInfo实例只读字段FieldHandle
  • RuntimeFieldHandle转FieldInfo:调用FieldInfo的静态方法GetFieldFromHandle
  • MethodInfo转RuntimeMethodHandle:查询MethodInfo实例只读字段MethodHandle
  • RuntimeMethodHandle转MethodInfo:调用MethodInfo的静态方法GetMethodFromHandle

示例代码

private static void Show(string s)
{
    Console.WriteLine("堆大小 : {0,12:##,###,###} - {1}", GC.GetTotalMemory(true), s);
}

static void Main()
{
    var bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

    Show("任何反射操作之前的堆的大小");

    //为MSCorlid.dll中所有方法构建MethodInfo对象缓存
    List methodBases = new List();
    foreach (var item in typeof(Object).Assembly.GetExportedTypes())
    {
        if (item.IsGenericTypeDefinition)
            continue;

        MethodBase[] mb = item.GetMethods(bf);
        methodBases.AddRange(mb);
    }
    Console.WriteLine("方法个数 : {0}", methodBases.Count);
    Show("绑定所有方法后堆的大小");

    //为所有MethodInfo对象构建RuntimeMethodHabdle缓存
    List runtimeMethodHandles = methodBases.ConvertAll(mb => mb.MethodHandle);
    Show("构建RuntimeMethodHandle后堆的大小");

    //阻止缓存被过早垃圾回收
    GC.KeepAlive(methodBases);

    //垃圾回收
    methodBases = null;
    Show("垃圾回收methodBases后堆的大小");

    Console.ReadKey();
}

结果

你可能感兴趣的:(c#,编程,反射)