反射(Reflection)

来源:互联网 发布:gta5 handling 数据 编辑:程序博客网 时间:2024/06/02 12:14

本文转自 http://blog.csdn.net/fantasiax/archive/2007/02/14/1510216.aspx

一.什么是反射(Reflection)

        在程序开发中,反射着实是个很酷的概念。为什么酷?因为反射是个物理名词吗——你把它放到程序设计里,一上来谁也听不懂,当然很酷啦!有多酷呢?反正我知道N多公司面试的时候都喜欢拿这个东东说事儿。你别说,有些设计模式的实现还真离不开使用反射。这么酷的东西,怎么可以不会呢?
        什么是反射呢?简言之就是软件开发中的“读心术”啦!比如我是一个DLL文件,我心里想的是:“明明白白我的心,渴望一份真感情……神啊救救我吧!”;而你会“读心术”,当你看到我的时候,我虽然没有开口告诉你,你却一眼就看穿我藏在心底的秘密、知道我在想什么了,就好像你的目光能穿透我的身体再反射回你眼里一样。反射就是这么得名滴——不但酷,还挺浪漫。

二.为什么需要反射

         用一句话来说,那就是——为了使软件具有良好的伸缩性(Make Software Flexible)。喔!又一个很酷的概念!软件又不是天线杆,怎么个“伸缩”法呢?

  •  伸:指的是软件功能的扩展。比如:为一个软件开发插件,为它增加新的功能。
  •  缩:指的是软件功能的削减。比如:软件的试用版、低付费版或者在使用者权限不够时,把相应的功能禁止或者去掉。

        你可能会问:以前没有.NET和反射的时候,难道软件就没有伸缩性了吗?当然有,只是实现起来要麻烦多了,比起实现对功能的扩展和削减来,将扩展和削减结果交付给客户(也就是软件的部署)会更加麻烦。

        举个例子:你使用VC写了一个小软件(使用VB/Delphi等原生Win32 App工具情况是一样的),现在有一万个人在用。你为这个小软件增加了一个新功能,而且这个新功能在主菜单里有一个菜单项。那么为了让这一万个用户都能使用这个新功能,你就不得不为这个新功能开发一个安装包(补丁包),让这一万个人都安装一下它才能得到这个软件得到升级。在Windows平台上,这个补丁包要做的事情有两件:

  •  用新的主界面(EXE)替换旧的
  • 把包含新功能的模块(DLL)拷贝到恰当的目录下(鬼知道用户们都是怎么安装的软件)

这样做就麻烦多多了:

  • 首先是安装包的制作:这可都是成本、都是Bug的来源!看看微软有多少人在测试各个版本、各个语言的安装包(场面很壮观的)你就知道了。
  • 然后是用户的安装体验:用户可不都是程序员,会不会安装是一说、喜欢不喜欢安装是另一说。再赶上哪位仁兄安装的目录比较怪,这补丁打上去后让系统死翘翘也不是不可能。
  • 安全问题:安装包肯定是EXE吧,是EXE就有可能成为病毒的载体耶!想象一下,用户安完我的补丁包后——咦!好多的熊猫耶!——那我真是跳进太平洋也洗不清了,估计后半生都得躲到火星上过了。

        也许你会问——照你这么吓唬人,软件升级岂不是做不成了?当然不是啦!各大厂商都有自己的高招。比如M$,是通过使用COM组件技术(每个COM组件都实现了统一的接口,ActiveX算是COM的一种,IE的插件就是ActiveX的)结合配置文件和注册表,来让软件升级的开发和部署过程变得统一,同时使用在线更新技术把安装过程隐藏起来(只是隐藏,仍然还是使用EXE安装);Adobe则是使用了PICA架构(基于C++资源文件实现的统一接口),方便开发人员为Photoshop、Illustrator等产品开发滤镜和插件,使用PICA架构开发出来的插件直接拷贝到特定的文件夹下就能被加入主框架。但是,这些办法只能是让软件的伸缩“不那么痛苦”,并不能让软件达到“伸缩自如”的地步。

        想要理解清楚这个,就要从程序的编译和反射的实现原理入手。

 三.反射的实现原理

        在以Java为先锋“虚拟机”技术流行起来之前,构成计算机应用程序的文件(如EXE、DLL)里包含的内容就是可以被计算机CPU直接识别和使用的二进制指令和数据。当用户通过双击/命令行来运行程序的时候,Windows的程序加载器就是将文件数据读入内存并进行简单的映射——映射成为程序的数据段、代码段等,然后让程序开始执行(如果程序已经被病毒感染,那么病毒代码也一并执行,CPU并不能识别一段代码是游戏还是病毒)。这种文件遵循PE(Portable Executable)格式,又可以直接被CPU执行,所以我们称之为“原生PE文件”。

        开发这样的程序使用的典型语言是C/C++,其它如VB/Delphi等与其用法大同小异。程序的开发步骤,从质的不同上可以分为三个“期”,可别小看这三个期,它们是非常重要的——

  1. 设计编码期:包括了数据、算法、逻辑、类抽象及设计、架构分析、编码几道工序。这时候,程序的代码就是文本文件。
  2. 编译链接期:包括了预编译、编译(可能是文本代码、汇编代码、二进制代码库的混编)、链接几道工序。
  3. 调试执行期:进行Debug或者最终用户的使用。

        编译期虽然是这三期中耗时最短的一期,但作用却是决定性的。一旦编译成功(没有出现编译期错误),源代码就被编译成原生PE文件,这就意味着程序已经“烧结固化”了,文件与文件之间的依赖关系也固定了下来——伸缩性的限制正来源于此。而且,这种被“烧结固化”的PE文件中所包含的函数与它的在设计编码期的“样子”比起来,已经面目全非了:

===========================================
……
        520  207 0005E079 PrintWindow
        521  208 00030D5C PrivateExtractIconExA
        522  209 0000B6AD PrivateExtractIconExW
        523  20A 0004F724 PrivateExtractIconsA
        524  20B 0000D06C PrivateExtractIconsW
        525  20C 0001704A PtInRect
        526  20D 0005E08D QuerySendMessage
        527  20E 0005E0A1 RealChildWindowFromPoint
        528  20F 0005C250 RealGetWindowClass
……
============================================

        这是从USER32.DLL文件里Dump出来的一些函数。你会发现,除了函数名得以保留之外,函数的返回值类型、参数个数和类型等都已经“丢失”了。或者说,在这个DLL里到底有些什么东西,这个DLL文件自己也说不清楚(不具有“自描述性”)。在这种情况下,如果想在其它项目中复用这个DLL文件,你就必需拥有一个与之配套的头文件(USER32.H)帮助它来说清楚文件里都有些什么——C/C++是静态语言,在编译的时候一定要知道函数的类型和参数列表才肯干活。这一情况对于EXE、DLL、LIB(静态库)是一样的。

反射的作用:

1. 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现 有对象中获取类型

2. 应用程序需要在运行时从某个特定的程序集中载入一个特定的类型,以便实现某个任务时可以用到反射。

3. 反射主要应用与类库,这些类库需要知道一个类型的定义,以便提供更多的功能

反射用法

  • 使用 Assembly 定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例。
  • 使用 Module 了解如下的类似信息:包含模块的程序集以及模块中的类等。您还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
  • 使用 ConstructorInfo 了解如下的类似信息:构造函数的名称、参数、访问修饰符(如 publicprivate)和实现详细信息(如 abstractvirtual)等。使用 TypeGetConstructorsGetConstructor 方法来调用特定的构造函数。
  • 使用 MethodInfo 来了解如下的类似信息:方法的名称、返回类型、参数、访问修饰符(如 publicprivate)和实现详细信息(如 abstractvirtual)等。使用 TypeGetMethodsGetMethod 方法来调用特定的方法。
  • 使用 FieldInfo 来了解如下的类似信息:字段的名称、访问修饰符(如 publicprivate)和实现详细信息(如 static)等;并获取或设置字段值。
  • 使用 EventInfo 来了解如下的类似信息:事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等;并添加或移除事件处理程序。
  • 使用 PropertyInfo 来了解如下的类似信息:属性的名称、数据类型、声明类型、反射类型和只读或可写状态等;并获取或设置属性值。
  • 使用 ParameterInfo 来了解如下的类似信息:参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。

反射的性能:

 使用反射来调用类型或者触发方法,或者访问一个字段或者属性时clr 需 要做更多的工作:校验参数,检查权限等等,所以速度是非常慢的。所以尽量不要使用反射进行编程,对于打算编写一个动态构造类型(晚绑定)的应用程序,可以采取以下的几种方式进行代替:

1. 通过类的继承关系。让该类型从一个编译时可知的基础类型派生出来,在运行时生成该类 型的一个实例,将对其的引用放到其基础类型的一个变量中,然后调用该基础类型的虚方法。

2. 通过接口实现。在运行时,构建该类型的一个实例,将对其的引用放到其接口类型的一个变量中,然后调用该接口定义的虚方法。

3.通过委托实现。让该类型实现一个方法,其名称和原型都与一个在编译时就已知的委托相符。在运行时先构造该类型的实例,然后在用该方法的对象及名称构造出该委托的实例,接着通过委托调用你想要的方法。这个方法相对与前面两个方法所作的工作要多一些,效率更低一些 

在设计模式实现中使用反射技术

   采用反射技术可以简化工厂的实现。

  (1)工厂方法:通过反射可以将需要实现的子类名称传递给工厂方法,这样无须在子类中实现类的实例化。

  (2)抽象工厂:使用反射可以减少抽象工厂的子类。

  采用反射技术可以简化工厂代码的复杂程度,在.NET项目中,采用反射技术的工厂已经基本代替了工厂方法。

  采用反射技术可以极大地简化对象的生成,对以下设计模式的实现也有很大影响。

  (1)命令模式:可以采用命令的类型名称作为参数直接获得命令的实例,并且可以动态执行命令。

  (2)享元模式:采用反射技术实例化享元可以简化享元工厂。

原创粉丝点击