zoukankan      html  css  js  c++  java
  • C#的反射(一)

    1.什么是元数据(MetaData)和反射(reflection)

    一般情况下我们的程序都在处理数据的读、写、操作和展示。但是有些程序操作的数据不是数字、文本、图片,而是程序和程序类型本身的信息。

    ①元数据是包含程序以及类型信息的数据,它保存在程序的程序集当中。

    ②程序在运行的时候,可以查看其他程序集或者其本身的元数据。这个行为就是反射。

    2.Type

    BCL声明了一个Type类型(它是抽象类),用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。

    由于Type是抽象类,所以它不能被实例化。而是在运行时,CLR创建从Type(RuntimeType)派生的类型的实例。当我们要访问这些实例的时候,CLR不会返回派生类的引用而是返回Type基类的引用。

    关于Type有如下重要的点:

    ①对于程序每一个需要用到的类型,CLR会穿件一个包含这个类型信息的Type类型的对象(真实的是上面说的派生的类型的实例)。

    ②程序中用到的每一个类型都会关联到独立的Type类的对两个象。

    ③无论创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例。就像下面的图表示的一样。创建了一个OtherClass的实例oc、以及两个MyClass的实例mc1和mc2,但是在堆上都只会有一个Type对象来的对应他们,如下面的图示:

    简单看一下Type这个类型,里面可以看到如下的一些方法和属性。

    官方文档更全面哦: https://docs.microsoft.com/zh-cn/dotnet/api/system.type?view=netframework-4.8

     

    3.学习如何获取一个Type类对象

    方法一:通过GetType方法

    object类型包含了一个GetType方法,它可以用来返回事例的Type对象引用。由于所有的类都是继承自object类型,所以所有的类都可以调用GetType来获得Type类型对象的引用。下面的图很好的说明了,基类、派生类和object之间的关系

    所以下面的代码,在遍历派生类的Field的时候才能,把基类的也输出出来。

    //基类
    class BaseClass
    {
        public int BaseField = 0;
    }
    
    //派生类
    class DerivedClass : BaseClass
    {
        public int DerivedField = 0;
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var bc = new BaseClass();
            var dc = new DerivedClass();
            BaseClass[] bca = new BaseClass[] { bc, dc };
            foreach(var v in bca)
            {
                //获取类型
                Type t = v.GetType();
                Console.WriteLine("Object Type: {0}", t.Name);
                //获取类中的字段
                FieldInfo[] fi = t.GetFields();
                foreach (var f in fi)
                    Console.WriteLine("     Field:{0}", f.Name);
                Console.WriteLine();
            }
            Console.WriteLine("End!");
            Console.ReadKey();
        }
    }

    结果:

    Object Type: BaseClass
         Field:BaseField
    Object Type: DerivedClass
         Field:DerivedField
         Field:BaseField
    End!

    方法二:还可以通过typeof()方法来获取一个类型的Type对象引用。例如下面的代码:

     Type t = typeof(DerivedClass);
    

    此外我们可以根据程序集来获取程序集内的类型

    //通过程序集获取类型
    var baseType = Assembly.GetExecutingAssembly().GetType("TestDemo.BaseClass");
    var derivedType = Assembly.GetExecutingAssembly().GetType("TestDemo.DerivedClass");
    

    4.常用的操作

    结合GetType和typeof操作,可以做很多事情。

    获取数组类型

    static void Main(string[] args)
    {
        var intArray = typeof(int).MakeArrayType();
        var int3Array = typeof(int).MakeArrayType(3);
    
        Console.WriteLine($"是否是int 数组 intArray == typeof(int[]) :{intArray == typeof(int[]) }");
        Console.WriteLine($"是否是int 3维数组 intArray3 == typeof(int[]) :{int3Array == typeof(int[]) }");
        Console.WriteLine($"是否是int 3维数组 intArray3 == typeof(int[,,]):{int3Array == typeof(int[,,]) }");
    
        //数组元素的类型
        Type elementType = intArray.GetElementType();
        Type elementType2 = int3Array.GetElementType();
    
        Console.WriteLine($"{intArray}类型元素类型:{elementType }");
        Console.WriteLine($"{int3Array}类型元素类型:{elementType2 }");
    
        //获取数组的维数
        var rank = int3Array.GetArrayRank();
        Console.WriteLine($"{int3Array}类型维数:{rank }");
        Console.ReadKey();
    }

    如上面的例子,

    MakeArrayType() 可以用来获取数组类型,有一个参数是数组的维数

    GetElementType() 可以用来获取数组元素的类型

    GetArrayRank() 可以获取数组的维数

    获取嵌套类型

    public class Class
    {
        public class Student
        {
            public string Name { get; set; }
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            #region 嵌套类型
            var classType = typeof(Class);
    
            foreach (var t in classType.GetNestedTypes())
            {
                Console.WriteLine($"NestedType ={t}");
                //获取一个值,该值指示 System.Type 是否声明为公共类型。
                Console.WriteLine($"{t}访问 {t.IsPublic}");
                //获取一个值,通过该值指示类是否是嵌套的并且声明为公共的。
                Console.WriteLine($"{t}访问 {t.IsNestedPublic}");
            }
    
            Console.ReadKey();
            #endregion
        }
    }

    输出:

    NestedType =TestDemo.Class+Student
    TestDemo.Class+Student访问 False
    TestDemo.Class+Student访问 True

    获取类型名称

    Type里面具有NameSpace、Name和FullName属性。一般FullName是两者的组合。但是对于嵌套类型和封闭式泛型不成立。可以参考下面的demo

    static void Main(string[] args)
    {
        #region 获取名称
        var type = typeof(Class);
        Console.WriteLine($"
    ------------一般类型-------------");
        PrintTypeName(type);
    
        //嵌套类型
        Console.WriteLine($"
    ------------嵌套类型-------------");
        foreach (var t in type.GetNestedTypes())
        {
            PrintTypeName(t);
        }
    
        var type2 = typeof(Dictionary<,>);            //非封闭式泛型
        var type3 = typeof(Dictionary<string, int>);  //封闭式泛型
    
        Console.WriteLine($"
    ------------非封闭式泛型-------------");
        PrintTypeName(type2);
        Console.WriteLine($"
    ------------封闭式泛型-------------");
        PrintTypeName(type3);
        Console.ReadKey();
        #endregion
    
    }
    
    private static void PrintTypeName(Type t)
    {
        Console.WriteLine($"NameSpace: {t.Namespace}");
        Console.WriteLine($"Name :{t.Name}");
        Console.WriteLine($"FullName: {t.FullName}");
    }

    结果:

    ------------一般类型-------------
    NameSpace: TestDemo
    Name :Class
    FullName: TestDemo.Class
    
    ------------嵌套类型-------------
    NameSpace: TestDemo
    Name :Student
    FullName: TestDemo.Class+Student
    NameSpace: TestDemo
    Name :Teacher
    FullName: TestDemo.Class+Teacher
    
    ------------非封闭式泛型-------------
    NameSpace: System.Collections.Generic
    Name :Dictionary`2
    FullName: System.Collections.Generic.Dictionary`2
    
    ------------封闭式泛型-------------
    NameSpace: System.Collections.Generic
    Name :Dictionary`2
    FullName: System.Collections.Generic.Dictionary`2[
    [System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],
    [System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

    获取基类类型和接口类型

    var base1 = typeof(System.String).BaseType;
    var base2 = typeof(System.IO.FileStream).BaseType;
    var base3 = typeof(DerivedClass).BaseType;
    
    Console.WriteLine($"base1 :{base1.Name}");
    Console.WriteLine($"base2 :{base2.Name}");
    Console.WriteLine($"base3 :{base3.Name}");
    
    foreach (var iType in typeof(Guid).GetInterfaces())
    {
        Console.WriteLine($"iType :{iType.Name}");
    }

    输出:

    base1 :Object
    base2 :Stream
    base3 :BaseClass
    iType :IFormattable
    iType :IComparable
    iType :IComparable`1
    iType :IEquatable`1

    此外Type还有两个方法:

    我们在判断某个实例对象是否是某个类型的时候,经常使用 is语句。

    Type中的方法 IsInstanceOfType 其实和is是等价的。

    var baseClassObject = new BaseClass();
    var check1 = baseClassObject is BaseClass;
    var check2 = base3.IsInstanceOfType(baseClassObject);
    Console.WriteLine($"使用is判断类型是否相同 :{check1}");  //结果True
    Console.WriteLine($"使用IsInstanceOfType类型是否相同 :{check2 }"); //结果True 

    返回结果都是True的。

    还有一个是 IsAssignableFrom ,它的作用是确定指定类型的实例是否可以分配给当前类型的实例。

    var base4 = typeof(BaseClass); //baseClass的实例
    var baseClassObject = new BaseClass();
    var derivedClassObject = new DerivedClass();
    var classObject = new Class();
    var checkResult1 = base4.IsAssignableFrom(baseClassObject.GetType()); //判断BaseClass类型是否可以分配给BassClass类型
    var checkResult2 = base4.IsAssignableFrom(derivedClassObject.GetType());  //判断DerivedClass类型是否可以分配给BassClass类型
    var checkResult3 = base4.IsAssignableFrom(classObject.GetType()); //判断Class类型是否可以分配给BassClass类型
    Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult1}");   //True
    Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult2}");   //True 
    Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult3}");  //False
    

    实例化类型

    I. 有两种方法可以动态的实例化类型。

    方法一 通过静态的 Activator.CreateInstance()方法创建,它有多个重载函数。

    var dateTime1 = (DateTime)Activator.CreateInstance(typeof(DateTime),2019,6,19);
    var dateTime2 = (DateTime)Activator.CreateInstance(typeof(DateTime), 2019,6,19,10,10,10);
    Console.WriteLine($"DateTime1: {dateTime1}"); //DateTime1: 2019/6/19 0:00:00
    Console.WriteLine($"DateTime2: {dateTime2}"); //DateTime2: 2019/6/19 10:10:10
    

    一般我们像上面一样都是传一个Type和构造函数的参数。当不存在这个构造函数的时候,就会抛出错误。

    方法二 调用ConstructInfo对象上面的Invoke方法,ConstructInfo对象是通过调用类型(高级环境)上的GetConstructor方法获取的。

    先分析一下场景,例如我有下面这样的一个类型:

    public class InvokeClass
    {
        private string _testString;
        private long _testInt;
        
        public InvokeClass(string abc)
        {
            _testString = abc;
        }
        public InvokeClass(StringBuilder abc)
        {
            _testString = abc.ToString();
        } 
        
       public InvokeClass(string abc,long def)
        {
            _testString = abc;
            _testInt = def;
        }    
    }

    存在两个构造函数,一个传入的是string类型,一个传入的是StringBuilder类型,此时如果我通过new 的方式去创建一个对象,并传入构造函数为null,那么就是报出下面的错误:说明存在二义性,也就是说找不到对应使用哪个来构造。

    同样的,如果我使用方法一 Activator.CreateInstance 去创建对象,会出现下面的问题:找不到对应的构造函数。

    但是采用ConstructInfo的方式就可以指定对应的构造函数了。类似如下代码

    //找到一个参数为string的构造函数
    var constructorInfo = typeof(InvokeClass).GetConstructor(new[] { typeof(string)});      
    //使用该构造函数传入一个null参数      
    var obj4 = (InvokeClass)constructorInfo.Invoke(new object[] { null });
    

    还可以结合查询来找到对应的构造函数

    //获取所有的构造函数
    var constructorInfoArray = typeof(InvokeClass).GetConstructors();
    //过滤一次,获取所有两个参数的构造函数
    var constructorInfoArray2 = Array.FindAll(constructorInfoArray, x => x.GetParameters().Length == 2);
    //最后找的第二个参数是long类型的构造函数
    var constructorInfo2 = Array.Find(constructorInfoArray2, x => x.GetParameters()[1].ParameterType == typeof(long));
    //如果存在,就创建对象
    if (constructorInfo2 != null)
    {
        var obj5 = (InvokeClass)constructorInfo2.Invoke(new object[] { "abc", 123 });
    }
    

    动态构造对象的缺点就是慢,简单对比一下,采用反射和new创建100万个对象,耗时对比还是比较明显的。

    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 100000; i++)
    {
        var obj3 = (InvokeClass)Activator.CreateInstance(typeof(InvokeClass), "abc", 123);
    }
    sw.Stop();
    Console.WriteLine($"时间:{sw.ElapsedMilliseconds}ms");
    
    var sw2 = new Stopwatch();
    sw2.Start();
    for (int i = 0; i < 100000; i++)
    {
        var obj = new InvokeClass("abc", 123);
    
    }
    sw2.Stop();
    Console.WriteLine($"时间:{sw2.ElapsedMilliseconds}ms");

    输出:

    时间:280ms
    时间:1ms

    II. 实例化委托

    动态创建静态方法和实例方法的委托传入的参数不太一样,使用的是CreateDelegate的重载,可以参考下面的例子

    /// <summary>
    ///  创建指定类型的委托,该委托表示要对指定的类实例调用的指定实例方法。
    /// </summary>
    /// <param name="type">要创建的委托的 System.Type</param>
    /// <param name="target"> 类实例,对其调用 method</param>
    /// <param name="method">委托要表示的实例方法的名称</param>
    /// <returns></returns>
    public static Delegate CreateDelegate(Type type, object target, string method);
    
    /// <summary>
    ///  创建指定类型的委托,该委托表示指定类的指定静态方法。
    /// </summary>
    /// <param name="type">要创建的委托的 System.Type</param>
    /// <param name="target">  表示实现 method 的类的 System.Type</param>
    /// <param name="method"> 委托要表示的静态方法的名称。</param>
    /// <returns></returns>
    public static Delegate CreateDelegate(Type type, Type target, string method);
    

    例如:

    class Program
    {    
        public static int StaticSum(int a, int b)   {
            return a + b;
        }
    
        public int InstanceSum(int a, int b)
        {
            return a + b;
        }
    
        //创建一个委托
        delegate int delegateOperate(int a, int b);
        static void Main(string[] args)
        {
            #region 实例化委托
            //静态方法的委托
            Delegate staticD = Delegate.CreateDelegate(typeof(delegateOperate), typeof(Program), "StaticSum");
            //实例方法的委托
            Delegate instanceD = Delegate.CreateDelegate(typeof(delegateOperate), new Program(), "InstanceSum");
            
            Console.WriteLine($"staticD:{staticD.DynamicInvoke(1,2)}");
            Console.WriteLine($"instanceD:{instanceD.DynamicInvoke(10,20)}");
            #endregion
            Console.ReadKey();
        }        
    }

    III.范型的实例化

    泛型分为封闭型和未封闭型,对于封闭类型的泛型是可以通过反射进行实例化的,而未封闭的泛型不能实例化。如下图所示:

    封闭式的泛型和未绑定的泛型是可以相互转换的。

    ①未绑定的泛型可以通过 MakeGenericType 变成封闭的

    ②封闭的可以通过GetGenericTypeDefinition 获取未绑定的类型。

    class Program
    {
        static void Main(string[] args)
        {
            Type closed = typeof(List<int>);
            Type unBound = typeof(List<>);
    
            //转换
            var newClosed = unBound.MakeGenericType(typeof(int));
            var newUnBound = closed.GetGenericTypeDefinition();
    
            Console.WriteLine($"List<int> 类型{closed}");
            Console.WriteLine($"List<> 类型{unBound}");
            Console.WriteLine($"List<> MakeGenericType执行后 类型{newClosed}");
            Console.WriteLine($"List<int> GetGenericTypeDefinition执行后 类型{newUnBound}");
        }
    }

    参考: 《C#图解教程》、《果壳中的C#》 

  • 相关阅读:
    【反射】Java反射机制
    Composer教程之常用命令
    Composer教程之基础用法
    Composer教程之初识Composer
    Composer 的结构详解
    现代 PHP 新特性系列(七) —— 内置的 HTTP 服务器
    现代 PHP 新特性系列(一) —— 命名空间
    现代 PHP 新特性系列(二) —— 善用接口
    现代 PHP 新特性系列(三) —— Trait 概览
    现代 PHP 新特性系列(四) —— 生成器的创建和使用
  • 原文地址:https://www.cnblogs.com/dcz2015/p/11058193.html
Copyright © 2011-2022 走看看