• 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#》 

  • 相关阅读:
    Python合集之Python正则表达式(三)
    Python合集之Python正则表达式(一)
    Python合集之Python字符串编码转换
    Python合集之Python字符串常用操作(五)
    Python合集之Python字符串常用操作(四)
    【EFCORE笔记】数据库SQL操作日志记录
    【EFCORE笔记】捕获审计日志数据
    【EFCORE笔记】数据库提供程序优化与单元测试
    【EFCORE笔记】仓储与工作单元模式
    【EFCORE笔记】属性变更事件追踪策略
  • 原文地址:https://www.cnblogs.com/dcz2015/p/11058193.html
Copyright © 2020-2023  润新知