手记

反射,反射--程序员的快乐?

引言

        前几天帮助一位网友解决了一个问题,大概是他们公司的老板做了一个项目,听他的描述项目不是很大,但是他们老板想要做到程序的解耦,也就是说他们封装了一个dll文件,在上层调用时不提供给他们引用关系,对外的设计保密,但是又想让上层使用这个dll文件,怎么办呢?我提供给他一种方法是使用反射,可能这不是最好的方法,但是很适合的方法。对于反射对于程序员来说已经不是新名词了,本文就来更深入的来讨论下反射。


一、何为反射?


        简单点说反射是使用另外一种方法来调用获取应用程序或者程序组件的一些信息,这个应用程序可以是我们正在运行的也可以是还没有运行的,只要是能够被.NET调用的文件都可以使用反射来获取文件的信息,如我们众所周知的.dll、.exe、com组件等都可以使用反射来获取文件的信息,同时也可以使用反射来调用这些组件或者程序。

       “反射”其实就是利用程序集的元数据信息。 反射可以有很多方法,编写程序时请先导入 System.Reflection 命名空间。

  1、反射获取类中的对象


  1. //假设你要反射一个 DLL 中的类,并且没有引用它(即未知的类型):   

  2. Assembly assembly = Assembly.LoadFile("程序集路径,不能是相对路径"); //加载程序集(EXE 或 DLL)  

  3. //或者 Assembly assembly=Assembly.LoadFrom();   //加载程序集   

  4. object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); //创建类的实例   


  2、若要反射当前项目中的类可以为



  1. Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集   

  2. object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); // 创建类的实例,返回为 object 类型,需要强制类型转换  



  3、也可以为


  1. Type type = Type.GetType("类的完全限定名,即类的名称");   

  2. object obj = type.Assembly.CreateInstance(type);   


补充:
        1)反射创建某个类的实例时,必须保证使用类的完全限定名(命名空间 + 类名)。Type.GetType 方法返回 null 则意味搜索元数据中的相关信息失败(反射失败),请确保反射时使用类的完全限定名。
       2)反射功能十分强大,没有什么不能实现的。若实现“跨程序集”,请使用第一种方法创建类的实例,并反射该实例的字段、属性、方法、事件... 然后动态调用之。



  4、反射深层思考

        .NET程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集、模块和类型的对象。



      



       我们可以使用反射动态地创建类型的实例,将类型绑定到现有对象或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。反射通常具有以下用途:

      (1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
      (2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
      (3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。
      (4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法来调用特定的方法。 
      (5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。 
      (6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。 
      (7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
      (8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。


 4.1 反射性能

       使用反射来调用类型或者触发方法,或者访问一个字段或者属性时clr 需 要做更多的工作:校验参数,检查权限等等,所以速度是非常慢的。所以尽量不要使用反射进行编程,对于打算编写一个动态构造类型(晚绑定)的应用程序,可以采取以下的几种方式进行代替:
      (1)通过类的继承关系。让该类型从一个编译时可知的基础类型派生出来,在运行时生成该类 型的一个实例,将对其的引用放到其基础类型的一个变量中,然后调用该基础类型的虚方法。
      (2)通过接口实现。在运行时,构建该类型的一个实例,将对其的引用放到其接口类型的一个变量中,然后调用该接口定义的虚方法。
      (3)通过委托实现。让该类型实现一个方法,其名称和原型都与一个在编译时就已知的委托相符。


二、获取对象并执行方法


        在获取到程序集中的对象后我们往往要调用对象的方法来实现某个功能,反射的强大不仅仅能获取程序集中的对象,而且可以执行对象中的方法。
        这里需要思考一个问题就是为什么要使用反射,何时使用反射。做过三层的程序员应该很快能够想到经典的三层结构,是的,在三层结构中为了实现程序和数据库的解耦使用了反射,去除了数据库的限制,另外呢。通过在实际的项目中发现,反射很多时候还应用在其它地方,如:程序集引用出问题,这时候可以使用反射来解除耦合,另外还可以解决类似开篇提到的那位网友的问题。
        Note:需要提醒的是,在.NET中使用反射时需要注意Framework的版本,反射只允许调用比本身Framework架构低的程序集,如果反过来使用反射调用高版本的Framework程序集 这时反射就会出错。
 

 2.1 三层+反射


         这里要做一个小插曲,在经典的三层结构中为了实现换DB的功能加入了工厂来反射创建D层,这种反射是在拥有接口类型的基础上获取D层对象并执行对象中的方法的。这里的接口只提供了一个方法架构,里面的虚方法是在具体的数据库层来实现的,通过反射来获取了DLL层中对象的方法。创建的对象有具体的实现但返回的类型却是接口,那是如何执行具体的方法的呢,请看下图。



    工厂类实现方法,采用了反射。

  1. public class Factory  

  2. {  

  3.     //获取DLL文件  

  4.     Assembly assembly = Assembly.LoadFile("DLL文件路径,完整路径,不可以为绝对路径");  

  5.     /// <summary>  

  6.     /// 工厂,反射创建需要调用的D层对象  

  7.     /// </summary>  

  8.     /// <returns>ReflectIDAL,需要调用的接口类</returns>  

  9.     public ReflectIDAL CreateDAL() {  

  10.   

  11.         ReflectIDAL ri; //声明接口层  

  12.         //要获取对象的名称,命名空间.程序集.类名  

  13.         ri = (ReflectIDAL)assembly.CreateInstance("ReflectDAL.ReflectDAL");  

  14.   

  15.         return ri;  

  16.     }  

  17. }<span style="font-family:SimSun;"><span style="font-size:10.5pt;">  

  18. </span></span>  


    IDAL和DAL两层中的代码示例。

  1. public interface ReflectIDAL  

  2. {  

  3.     /// <summary>  

  4.     /// IDAL方法,获取表中的数据  

  5.     /// </summary>  

  6.     /// <returns>Boolean,True查询成功,False查询失败</returns>  

  7.     public Boolean SelectFromTable();  

  8. }  

  9.   

  10.   

  11. public class ReflectDAL:ReflectIDAL  

  12. {  

  13.     /// <summary>  

  14.     /// DAL方法,获取表中的数据  

  15.     /// </summary>  

  16.     /// <returns>Boolean,True查询成功,False查询失败</returns>  

  17.     public bool SelectFromTable()  

  18.     {  

  19.         //具体实现的方法  

  20.   

  21.         //返回值  

  22.         return true;  

  23.     }  

  24. }  


     BLL层方法,方法调用的核心,实际调用了IDAL层的虚方法,真正的实现者为DAL层的方法。

  1. namespace ReflectDemo  

  2. {  

  3.     public class ReflectBLL  

  4.     {  

  5.         /// <summary>  

  6.         /// 调用IDAL层方法获取表中的数据  

  7.         /// </summary>  

  8.         /// <returns>bool,True查询成功,False查询失败</returns>  

  9.         public bool GetFromTable()  

  10.         {  

  11.             Boolean boolReturn; //声明返回值  

  12.             Factory factory = new Factory();    //创建工厂  

  13.             ReflectIDAL ri = factory.CreateDAL();   //获取需要调用的对象  

  14.   

  15.             //调用IDAL的方法,实际者为执行DAL层方法  

  16.             //采用了动态联编指定了虚方法SelectFromTable的实现者  

  17.             boolReturn = ri.SelectFromTable();    

  18.             //具体实现的方法  

  19.             return boolReturn;  

  20.         }  

  21.     }  

  22. }<span style="font-family:SimSun;"><span style="font-size:10.5pt;">  

  23. </span></span>  





  2.1.1 联编  

        联编就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址,它是计算机程序彼此关联的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。
        静态联编是指在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定,在编译阶段就必须了解所有的函数或模块执行所需要检测的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型。
        动态联编是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定,动态联编对函数的选择不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。
        上图中B层在调用接口层实现功能时使用的就是动态联编,对于很多程序员来说它可能是一个新名词,对于不经常思考的程序员来说它必须是一个新名词。B层返回的方法是接口,在执行方法时调用的是接口的方法,但是在实现时调用的却是DAL层中的方法,这就是动态联编的过程。
         有关更多函数联编的内容,请下载:面向对象中函数的联编机制
        

 2.2 不知道类及方法


          经典的三层结构中,通过使用接口来获取了对象并执行对象的方法,很简单。但是很多情况下我们使用的dll文件没有提供给你类的类型,只是通过反射来获取了一个object对象,那如何通过反射来获取程序集中的对象和调用对象中的方法呢。别着急。

  1. /// <summary>  

  2. /// 获取反射的DLL文件中的方法,并执行方法  

  3. /// </summary>  

  4. /// <returns>bool,True成功,False失败</returns>  

  5. public bool ExeReflectMethod()  

  6. {  

  7.     Assembly assembly;//定义程序集  

  8.     assembly = Assembly.LoadFrom("DLL文件路径");  //加载程序集并赋给新定义的程序集  

  9.   

  10.     //获取程序集中类型的集合,该集合是程序集中的类的集合  

  11.     //在程序集中创建类后按照字母的先后次序进行排列,字母靠前的索引就小,在获取类别是只能指定类所在的索引号不能使用字符串  

  12.     Type[] type=assembly.GetTypes();  

  13.   

  14.     //加载类中的方法,使用MethodInfo定义方法  

  15.     //使用GetMethod给声明的方法赋值  

  16.     MethodInfo method = type[0].GetMethod("需要执行的type(0)中的方法名");  

  17.   

  18.     //定义参数,该参数是为上面指定的方法传递的参数  

  19.     string str = "12";  

  20.   

  21.     //定义并实例化一个该对象,使用CreateInstance实例化时,一定要注意它实例化的是类中的一个构造方法  

  22.     //至于如何获取想要创建的一个类所在程序集中的类型数组,这时需要我们调试,执行GetTypes后查看数组的内容,获取加载的类所在的索引  

  23.     object obj = Activator.CreateInstance(type[0], true);    //另一种方法o=assembly.CreateInstance(a(0).ToString,True)  

  24.   

  25.     //使用Invoke调用实例化该类中的一个方法  

  26.     Label1.Text = method.Invoke(obj,new object[]{str});  

  27. }  


       Note:示例采用的是GetTypes获取了dll文件的所有类,它返回的是一个类型的集合,如果想要获取集合中的某个类建议在调试状态下来查看下GetTypes中的每个类来确定想要使用的类在集合中的位置。

       举一反三:
上面的示例只给出了如何使用反射调用一个完全陌生的dll文件中的类,并执行类中的方法。由调用的方法应该能联想到是不是可以获取类中的属性,并给属性赋值,如果此时调用的是接口怎么办,同样能够反射获取接口中的方法吗?
        
         当然可以,能通过反射来获取类中的所有方法和属性,只要我们能够想到的类的特性,反射都能够为我们实现。因为反射不但为我们提供了GetMethod方法更提供了GetProperty,GetMember,GetField,GetEvent等方法来操作类,有关更多使用方法请查阅微软官方MSDN。

        所以反射技术不但为程序员提供了强大的调用DLL的方法,更能够在架构设计时为我们提供便利,通过反射能够获取类中的所有特性,这种技术就好像是反编译一样,为程序员编写代码提供了便利。       


结语

        文章重点在分析反射的基础,在这里读者可能会有很多疑惑,我们对反射进行了多角度的解读,但是唯一没有解读到的是有关反射的机制,.NET反射大量使用了泛型,并使用了IEnumerable接口遍历对象,很简单。
        文章在第一部分着重介绍了基本内容,第二部分着重介绍了反射调用方法的过程,其中介绍了两种方法,一种是在传统的三层中使用反射获取了对象并转化为接口类型为的是达到能够灵活更换数据库的目的,期间穿插了一些有关联编的内容,概念很容易理解;另外一种方法是对DLL文件完全陌生的情况下调用dll中类的方法。

原文出处

2人推荐
随时随地看视频
慕课网APP