• 反射原理及简介


    一.什么是反射

     Reflection,中文翻译为反射。这是.Net中获取运行时类型信息的方式,

    .Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,

    例如:Assembly类可以获得正在运行的程序集信息,也可以动态的加载程序集,以及在程序集中查找类型信息,并创建该类型的实例。

    Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。

    MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。诸如此类,

    还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。

     二. 关于程序集和命名空间的关系

    很多人对这个概念可能还是很不清晰,对于合格的.Net程序员,有必要对这点进行澄清。

    程序集是.NET应用程序执行的最小单元,编译出来的.dll和.exe都是程序集。 程序集和命名空间的关系不是一一对应,也不互相包含,一个程序集里面可以有多个命名空间,一个命名空间也可以在多个程序中存在,这样说可能有点模糊,举个例子:

    程序集A包含两个命名空间:

    namespace  N1
    {
          public  class  AC1  {…}
          public  class  AC2  {…}
    }
    namespace  N2
    {
          public  class  AC3  {…}
          public  class  AC4{…}
    }

    程序集B包含两个命名空间:

    namespace  N1
    {
          public  class  BC1  {…}
          public  class  BC2  {…}
    }
    namespace  N2
    {
          public  class  BC3  {…}
          public  class  BC4{…}
    }

    这两个程序集中都有N1和N2两个命名空间,而且各声明了两个类,这样是完全可以的,然后我们在一个应用程序中引用程序集A,那么在这个应用程序中,我们能看到N1下面的类为AC1和AC2,N2下面的类为AC3和AC4。

    接着我们去掉对A的引用,加上对B的引用,那么我们在这个应用程序下能看到的N1下面的类变成了BC1和BC2,N2下面也一样。 如果我们同时引用这两个程序集,那么N1下面我们就能看到四个类:AC1、AC2、BC1和BC2。

    到这里,我们可以清楚一个概念了,命名空间只是说明一个类型是那个族的,比如有人是汉族、有人是回族;而程序集表明一个类型住在哪里,比如有人住在北京、有人住在上海;那么北京有汉族人,也有回族人,上海有汉族人,也有回族人,这是不矛盾的。

    上面我们说了,程序集是一个类型居住的地方,那么在一个程序中要使用一个类,就必须告诉编译器这个类住在哪儿,编译器才能找到它,也就是说必须引用该程序集。

     那么如果在编写程序的时候,也许不确定这个类在哪里,仅仅只是知道它的名称,就不能使用了吗?答案是可以,这就是反射了,就是在程序运行的时候提供该类型的地址,而去找到它。

     

     三.为什么使用反射

     有人会有疑问,程序所用的类既然可以事先写好,那么为什么还要在程序运行的时候去生成,这样岂不是浪费系统资源。存在就是合理的,既然微软给我们开发这项技术,肯定是这个这个东西有需求,举个简单的例子:

    现在要开发一个报表打印模块,有的企业要求数据以Excel报表,有的企业要求打印水晶报表等等,这个时候我们就可以先定义一个接口,任何报表打印方法都必须实现这个接口:

       public interface  IReport
        {
            void StartPrint();
        }

    我们通过配置文件可以加载对应的读取报表的类型,因为报表打印类都实现了IReport的接口,所以都可以通过反射的方式强转为该接口类型,这样就可以实现无需修改底层代码就可以实现打印不同的数据报表,符合我i们程序设计的开闭原则,这个就是反射最经典的应用。

    public static class Factory
    {
    //【1】读取配置文件
    static string reportType = ConfigurationManager.AppSettings["ReportType"].ToString();
    //【2】使用反射创建实现接口类的对象并以接口类型返回
    public static IReport ChooseReportType()
    {
    return (IReport)Assembly.Load("UseFactory").CreateInstance("UseFactory." + reportType);
    }
    }

    四.如何使用反射获取类型

    获取类信息有两种方式:

    第一种方式,得到实例化对象。 这个时侯我仅仅是得到这个实例对象,得到的方式也许是一个object的引用,也许是一个接口的引用,但是我并不知道它的确切类型,我需要了解,那么就可以通过调用System.Object上声明的方法GetType来获取实例对象的类型对象,比如在某个方法内,我需要判断传递进来的参数是否实现了某个接口,如果实现了,则调用该接口的一个方法。

       public void Progress(object o)
            {
                Type objType = o.GetType();
    
                if (objType.GetInterface("ITest") !=null)
                {
                    //调用该接口的方法
                }
            }

    第二种获取类型的方法是通过Type.GetType以及Assembly.GetType方法,但是在使用该方法时我们需要注意一些问题点。在程序集A.dll中需要反射程序集B.dll中的类型。如果使用稍有不慎,就会产生运行时错误。例如使用Type.GetType("BNameSpace.ClassName")在程序集A.dll获取程序集B.dll中的类型,就会返回Null。

    关于跨程序集的反射,有两点需要注意:

    1、如果使用typeof,编译能通过,则跨程序集的反射一定可以正常运行。可以说,typeof是支持强类型的。比如

     Type supType = typeof(BNameSpace.SubSpace.Class);
    

    如果当前程序集没有添加对EnterpriseServerBase.dll的引用,则编译会报错。

    2、如果使用Type.GetType来进行反射的话,情况就复杂些。这是因为Type.GetType是非强类型的。Type.GetType的参数是一个string为类型的完全限定名,如果当string表示的目标类型不在当前程序集中,则运行时Type.GetType会返回null。解决的办法是:首先加载目标程序集,然后再使用Assembly.GetType方法来获取类型。如:

    Assembly asmb = Assembly.LoadFrom("EnterpriseServerBase.dll") ;
    Type supType = asmb.GetType("EnterpriseServerBase.DataAccess.IDBAccesser") ;
    

    注意:当使用Type.GetType的时候,即使你添加了对EnterpriseServerBase.dll的引用,Type.GetType("EnterpriseServerBase.DataAccess.IDBAccesser")也会返回null,这是因为Type.GetType只会在当前程序集中进行类型搜索。

    5.如何根据类型动态创建对象

    第一种方式

    public class Example
    {
        static void Main()
        {
            // Create an instance of the StringBuilder type using 
            // Activator.CreateInstance.
            Object o = Activator.CreateInstance(typeof(StringBuilder));
    
            // Append a string into the StringBuilder object and display the 
            // StringBuilder.
            StringBuilder sb = (StringBuilder) o;
            sb.Append("Hello, there.");
            Console.WriteLine(sb);
    
            // Create an instance of the SomeType class that is defined in this 
            // assembly.
            System.Runtime.Remoting.ObjectHandle oh = 
                Activator.CreateInstanceFrom(Assembly.GetEntryAssembly().CodeBase, 
                                             typeof(SomeType).FullName);
    
            // Call an instance method defined by the SomeType type using this object.
            SomeType st = (SomeType) oh.Unwrap();
    
            st.DoSomething(5);
        }
    }

    例二:根据有参数的构造器创建对象

    namespace  TestSpace  
    {
      public  class  TestClass
          {
          private  string  _value;
          public  TestClass(string  value)  
        {
          _value=value;
          }
      }
    }
    
    Type  t  =  Type.GetType(“TestSpace.TestClass”);
    Object[]  constructParms  =  new  object[]  {“hello”};  //构造器参数
    TestClass  obj  =  (TestClass)Activator.CreateInstance(t,constructParms);
    

    把参数按照顺序放入一个Object数组中即可

    第三种方式:

    假如我们并没有加载对应.cs文件的程序集,那么我们该如何简洁的创建对象类型呢:

        object objCal =Assembly.LoadFrom("CalDLL.dll").CreateInstance("CalDLL.Calculator");

    是不是很简洁,当然我们也可以先获取对象的类型,再用Activator.CreateInstance 来创建对象,不过稍显麻烦:

     Assembly objAssembly = Assembly.LoadFrom("CalDLL.dll");
    
    
     Type objType = objAssembly.GetType("CalDLL.Calculator");
    
    
     object objCal = Activator.CreateInstance(objType);

    这样也能达到相同的效果。

    6.如何获取方法以及动态调用方法

    应用反射动态调用方法的示例如下:

    using System;
    using System.Reflection;
    
    class Program
    {
        // Methods to get:
    
        public void MethodA(int i, int j) { }
    
        public void MethodA(int[] i) { }
    
        public unsafe void MethodA(int* i) { }
    
        public void MethodA(ref int r) {}
    
        // Method that takes an out parameter:
        public void MethodA(int i, out int o) { o = 100;}
    
    
      static void Main(string[] args)
      {
        MethodInfo mInfo;
    
        // Get MethodA(int i, int j)
        mInfo = typeof(Program).GetMethod("MethodA",
            BindingFlags.Public | BindingFlags.Instance,
            null,
            CallingConventions.Any,
            new Type[] { typeof(int), typeof(int) },
            null);
        Console.WriteLine("Found method: {0}", mInfo);
    
        // Get MethodA(int[] i)
        mInfo = typeof(Program).GetMethod("MethodA",
            BindingFlags.Public | BindingFlags.Instance,
            null,
            CallingConventions.Any,
            new Type[] { typeof(int[]) },
            null);
        Console.WriteLine("Found method: {0}", mInfo);
    
        // Get MethodA(int* i)
        mInfo = typeof(Program).GetMethod("MethodA",
            BindingFlags.Public | BindingFlags.Instance,
            null,
            CallingConventions.Any,
            new Type[] { typeof(int).MakePointerType() },
            null);
        Console.WriteLine("Found method: {0}", mInfo);
    
        // Get MethodA(ref int r)
        mInfo = typeof(Program).GetMethod("MethodA",
            BindingFlags.Public | BindingFlags.Instance,
            null,
            CallingConventions.Any,
            new Type[] { typeof(int).MakeByRefType() },
            null);
        Console.WriteLine("Found method: {0}", mInfo);
    
        // Get MethodA(int i, out int o)
        mInfo = typeof(Program).GetMethod("MethodA",
            BindingFlags.Public | BindingFlags.Instance,
            null,
            CallingConventions.Any,
            new Type[] { typeof(int), typeof(int).MakeByRefType() },
            null);
        Console.WriteLine("Found method: {0}", mInfo);
    
      }
    }
    关于GetMethod()的更多信息可以通过如下:

    https://docs.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=netframework-4.8

    7.如何动态的获取属性信息:

    下面的例子简单的说明了如何获取属性信息

     void GetProperty()
            {
                Type objType = typeof(string);
               PropertyInfo[]  obj =  objType.GetProperties();
               if (obj !=null)
               {
                   foreach (var item in obj)
                   {
                       this.textBox1.Text = this.textBox1.Text + item.Name.ToString() + "\r\n";
                   }
               }
            }
  • 相关阅读:
    C#/.NET CTS和CLS:公共类型系统和公共语言规范
    MongoDB分布式集群架构(3种模式)
    Elastic Search 入门
    转:计算机视觉部分会议/期刊名称缩写
    神经网络模型的参数量和FLOPs的计算,torchstat,thop,循环神经网络RNN
    latex自己调配颜色
    latex图像和表格手动设置位置(清除自动寻找位置)
    转:图像泊松融合
    latex调整表格行高、列宽和表格整体的左右宽度
    转:目标检测评价标准mAP
  • 原文地址:https://www.cnblogs.com/Artist007/p/11022495.html
Copyright © 2020-2023  润新知