.Net开发笔记(二十一) 反射在.net中的应用
反射概念在网上到处都有,但是讲到的具体的应用很少,一个重要的原因是现实中真的很少用得到它。引用msdn上对“反射”的解释:
"通过 System.Reflection 命名空间中的类以及 System.Type,您可以获取有关已加载的程序集和在其中定义的类型(如类、接口和值类型)的信息。 您也可以使用反射在运行时创建类型实例,以及调用和访问这些实例。"
这个解释着实让人难以理解,至少对新手来讲,一头雾水。那么这篇文章我首先从概念下手,用一种尽量易于理解的方式解释一下反射到底是个什么东西。文章最后附加一个“反射”应用的demo,它能监听任何一个程序集中的任何一个Control(深度优先顺序遍历所有子控件)的所有事件信息。
在程序开发阶段,如果我们要使用一个类型(包括实例化该类型对象,访问对象等等操作),分三个步骤:
- 添加包含了这个类型程序集的引用(.net自带的类型程序集默认已引用);
- 代码中直接使用该类型(用一种文本字符的形式,比如类型名、方法名、属性名);
- 编译正确通过,程序运行。

图1
以上是我们常用的开发步骤,几乎不用去想为什么要这样,每个人都会。这个流程中第一个前提就是“要引用包含了这个类型的程序集”。假设某一次开发过程中,我们不能提前引用到包含这个类型的程序集(先不要否定这种情况,只能说明你没碰到),那么我们改怎么写代码?正常情况下,我们访问A类是这样的(假设A类在A.dll程序集中):
A a=new A();
a.PropertyName=””;
a.EventName+=a_EventName;
a.DoSomething();
正常编译通过。但是现在我们没有引用A.dll,我们改怎么写代码?还是像上面那样写吗?不对,因为编译通不过,编译器会提示“缺少对程序集的引用”(这个很容易理解,因为你没有引用程序集,编译器肯定不会知道)。
以上就是我们会碰到的一种情况,即:
在有些时候,我们可能会使用一种数据类型,但是开发阶段并不能引用到包含该类型的程序集,包含了这个类型的程序集只能在程序运行起来之后,动态的引用进来。开发阶段引用程序集如果叫“静态引用程序集”,那么运行时引用程序集就应该叫“动态引用程序集”了。后者会造成一个问题:我们该怎么写代码去访问动态引用程序集中的类型?

图2
这个时候,反射的作用就出来了。反射能够让我们使用在编译阶段编译器不知道的数据类型(注意是编译器不知道,不是我们不知道)。再举前面使用A类型的例子,我们在开发阶段没有引用A.dll程序集,因此下面的代码无法通过编译:
A a=new A();
a.PropertyName=””;
a.EventName+=a_EventName;
a.DoSomething();
但是,我们知道程序运行之后,可以动态引用到A.dll,那么现在代码中怎么使用A类型呢?看下面的代码:
Assembly assembly=Assembly.LoadFile(“C:\\A.dll”); //动态引用A.dll
Type t = assembly.GetType(“ReflectionTestNS.A”); //获取A类型在程序集中的信息
object oj=Activator.CreateInstance(t); //类似new A()
PropertyInfo p = t.GetProperty("PropertyName");
if (p != null)
{
p.SetValue(oj, “”, null); //类似oj.PropertyName=”123”
}
EventInfo ei = t.GetEvent("EventName");
if (ei != null)
{
Type tt = ei.EventHandlerType;
ei.AddEventHandler(oj, Delegate.CreateDelegate(tt, this, "oj_EventName"));
//类似oj.EventName+=oj_EventName
}
MethodInfo m = t.GetMethod("DoSomething", new Type[] { });
if (m != null)
{
m.Invoke(oj, null); //类似oj.DoSomething()
}
上面代码能够通过编译,我们可以通过以上代码去访问A.dll程序集中的A类型,即使在开发阶段我们没有A.dll的引用。需要注意的几点有:
1)虽然我们在开发阶段不能引用到A.dll程序集,但是我们应该对A.dll中的类型有了解,知道命名空间,知道数据类型名称,知道方法名称参数类型,知道事件名称委托类型等等,也就是说,虽然编译器不知道A.dll中的类型信息,我们开发人员必须知道A.dll中的类型信息,这样以来,我们才能利用“反射”加上“文本字符”作为标示去访问这个类型。
2)1)中规定的开发人员必须了解A.dll中的类型信息,仅仅是当你需要详细的使用一个类型对象时,如果你只需要获取A.dll中的有哪些类型、每个类型有哪些方法参数属性事件等,然后将他们的信息显示出来,完全没必要知道A.dll中的类型信息,比如VS中编辑器的智能提示功能,或者Reflector等利用反射实现数据集中类型信息显示的软件,它们的开发人员知道你的程序集信息吗?不知道,但是还是能工作很好。但是就上面“使用A类型”的例子来讲,你必须知道A.dll中的A类型中有个叫EventName的事件,你才能给它的对象注册事件,否则可以说你根本使用不了A类型的对象。
3)编译阶段,对象和方法就可以关联起来(比如a.DoSomething()能通过编译),这种如果称之为“早期绑定”(early binding),那么通过反射将对象和方法关联起来就称为“晚期绑定”(late binding)。前者在编译阶段编译器可以检查正确性,后者编译器无能为力,因为编译器不知道A.dll的任何信息。
一张图区分两种访问程序集中类型的区别:

图3
个人认为,正常开发中用不到反射,所以尽量避免使用反射(反射有缺陷,运行性能编译阶段不能检查正确性等),本系列博客(十七)中讲到的扩展应用程序,就使用到了反射,文中指出将插件打包成dll程序集后,放入宿主程序的plugins目录中,宿主程序启动后,会动态引用plugins目录中的程序集,动态创建插件类型实例,然后访问它。那么如果你是宿主程序的开发人员,你会在开发阶段引用到第三方开发的插件程序集dll文件吗?不能,但是你还是得在代码中使用它的类型。
注:上面扩展应用程序中不全使用反射去访问动态引用程序集中的类型,因为它使用到了一个IPlugin的接口,动态实例化插件对象后,是使用IPlugin接口引用这个对象,之后所有的都是通过这个接口去访问对象(之后没有使用到反射),它避免了使用反射的性能问题和在编译阶段能够检查程序的正确性(开发阶段宿主程序能够引用IPlugin接口程序集),这个也是必须使用反射场合的一种改进,后续有机会我会详细说明。
另外网上有很多讲述反射的文章,都是用类似如下代码作为反射应用实例,
void btn1_Click(object sender,EventArgs e)
{
Type t = typeof(Button);
//或者
Type t = btn1.GetType();
PropertyInfo p = t.GetProperty(“Text”);
if(p!=null)
{
p.SetValue(btn1,””,null); //利用反射编辑btn1的Text属性
}
}
以上类似代码并没有错误,只是我觉得会给人误导,反射的真正使用场合不在这里(这里完全用不着,为什么不直接使用btn1.Text=”123”呢?),看多了,人们就会认为反射就是这作用,用在这里。
Demo中包含了两个项目,一个是简单的说明了正常方法使用BackgroundWorker这个类型,和动态引用程序集动态创建BackgroundWorker类型对象(假装开发阶段没有引用包含BackgroundWorker类型的程序集),两者的区别。另一个项目能够动态引用程序集,并且动态实例化Control类实例,关键还能监听任何控件的所有事件,然后输出事件信息,这个有点复杂,不仅仅使用到了System.Reflection命名空间中的类型,还用了System.Reflection.Emit命名空间中的类型,后者可以动态创建类型,由于每个控件的每个事件类型不一样,并且个数还不确定,所以我们没有办法事先定义一个通用的事件注册者,只能挨个为每个事件动态创建一个事件注册者类。第二个项目流程见下图:

图4
第二个项目参见了CodeProject上老外的一篇文章(http://www.codeproject.com/Articles/3317/ControlInspector-monitor-Windows-Forms-events-as-t),注释请参见我的,代码中有详细的中文解释。
Demo截图:

图5 静态引用程序集访问类型 和 动态引用程序集访问类型的区别

图6 反射应用
总之,反射能够让你使用在编译阶段还不可达的程序集(类型)。
源码下载地址:http://files.cnblogs.com/xiaozhi_5638/ReflectionTest.rar
希望有帮助!
.Net开发笔记(二十一) 反射在.net中的应用的更多相关文章
- Django开发笔记二
Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.xadmin添加主题.修改标题页脚和收起左侧菜单 # ...
- Modbus库开发笔记之十一:关于Modbus协议栈开发的说明(转)
源: Modbus库开发笔记之十一:关于Modbus协议栈开发的说明
- python3.4学习笔记(二十一) python实现指定字符串补全空格、前面填充0的方法
python3.4学习笔记(二十一) python实现指定字符串补全空格.前面填充0的方法 Python zfill()方法返回指定长度的字符串,原字符串右对齐,前面填充0.zfill()方法语法:s ...
- 牢记!SQL Server数据库开发的二十一条注意点
如果你正在负责一个基于SQL Server的项目,或者你刚刚接触SQL Server,你都有可能要面临一些数据库性能的问题,这篇文章会为你提供一些有用的指导(其中大多数也可以用于其它的DBMS). ...
- SQL Server数据库开发的二十一条军规
如果你正在负责一个基于SQL Server的项目,或者你刚刚接触SQL Server,你都有可能要面临一些数据库性能的问题,这篇文章会为你提供一些有用的指导(其中大多数也可以用于其它的DBMS).在这 ...
- Modbus库开发笔记之十一:关于Modbus协议栈开发的说明
对于Modbus协议栈的整个开发内容,前面已经说得很清楚了,接下来我们说明一下与开发没有直接关系的内容. 首先,关于我为什么开发这个协议栈的问题.我们的初衷只是想能够在开发产品时不用每次都重写这一部分 ...
- SDL开发笔记(二):音频基础介绍、使用SDL播放音频
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- EasyUI 开发笔记(二)
接上篇 :EasyUI 开发笔记(一) (http://www.cnblogs.com/yiayi/p/3485258.html) 这期就简单介绍下, easyui 的 list 展示, 在easy ...
- (C/C++学习笔记) 二十一. 异常处理
二十一. 异常处理 ● 异常的概念 程序的错误通常包括:语法错误.逻辑错误.运行异常. 语法错误指书写的程序语句不合乎编译器的语法规则,这种错误在编译.连接时由编译器指出. 逻辑错误是指程序能顺利运行 ...
- Vue-cli开发笔记二----------接口调用、配置全局变量
我做的一个项目,本身是没用任何框架,纯手写的前端及数据交互,项目已经完结.最近学Vue,于是借用这个项目,改装成vue项目. (一)接口问题:使用axios的调用方法,proxyTable解决开发环境 ...
随机推荐
- make: *** [out/host/linux-x86/obj/EXECUTABLES/aidl_intermediates/aidl] 错误 1,make: *** [out/host/linux-x86/obj/lib/libESR_Portable.so] 错误 1
错误3: g++: g++: selected multilib '32' not installed selected multilib '32' not installed make: *** [ ...
- AOJ 0118 Property Distribution【DFS】
题意:在H * W的矩形果园里有苹果.梨.蜜柑三种果树, 相邻(上下左右)的同种果树属于同一个区域,给出果园的果树分布,求总共有多少个区域. 输入:多组数据,每组数据第一行为两个整数H,W(H < ...
- 虚拟机主机能互相ping通,但是无法远程连接
首先将虚拟机关机,找到Edit virtual machine Settings(编辑虚拟机设置)
- Scrapy shell调试网页的信息
通过scrapy shell "http://www.thinkive.cn:10000/zentaopms/www/index.php?m=user&f=login"
- selenium使用笔记(二)——Tesseract OCR
在自动化测试过程中我们经常会遇到需要输入验证码的情况,而现在一般以图片验证码居多.通常我们处理这种情况应该用最简单的方式,让开发给个万能验证码或者直接将验证码这个环节跳过.之前在技术交流群里也跟朋友讨 ...
- VS2012 还原默认设置
恢复默认设置的2种方法 如果VS出现问题或设置变乱,可以通过恢复默认设置使之回到安装成功时的状态,从而解决出现的问题.VS恢复默认设置的方法有2种,分别是:通过"导入和导出设置"实 ...
- 一些关于HTML与CSS的总结与实际应用
//学习前端也快一年了,觉得有必要好好总结一下这一年来学过的知识.一些是前辈们的精华,文章最后会讲地址一一放出,若原作者有任何介意,请及时联系我删除. 关于DOCTYPE 1.DOCTYPE的作用是什 ...
- Docker Volume 之权限管理(转)
Volume数据卷是Docker的一个重要概念.数据卷是可供一个或多个容器使用的特殊目录,可以为容器应用存储提供有价值的特性: 持久化数据与容器的生命周期解耦:在容器删除之后数据卷中的内容可以保持.D ...
- 在iPhone上同时关闭语音控制和siri的方法
分享 步骤及要点:1.在设置里打开siri.语音控制就自动关闭了.2.在siri里的"仅语言拨号"语言项里选择"土耳其文"或者"阿拉伯文". ...
- 国内外三个不同领域巨头分享的Redis实战经验及使用场景
Redis不是比较成熟的memcache或者Mysql的替代品,是对于大型互联网类应用在架构上很好的补充.现在有越来越多的应用也在纷纷基于Redis做架构的改造.首先简单公布一下Redis平台实际情况 ...