.NET之反射

第一版

前言

关于反射的内容,向来是隐藏在C#高级编程类别中的大部头书籍中,C#入门啊C#精通啊基本少有提及,若是提到也是模棱两可,叫你难以捉摸。 本文本着实用的目的,以《C#与.NET4高级程序设计(第5版)》第四部分,第15章为依据,部分讲述了.NET中反射的用法来分析下反射的实现及简单应用,不涉及原理性能问题的讨论。

在开始反射之前,以下内容需要提前了解,可阅读书中第14章内容:

  • 命名空间
  • 程序集
  • 元数据
第十五章内容略图

反射

反射(Reflection)是一个运行库类型发现的过程。使用反射服务,可以通过编程使用一个友好的对象模型得到元数据信息.

反射提供了描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。

反射(C# 和 Visual Basic)MSDN

用 .NET Framework 编程时利用反射,可以使用 System.Reflection 命名空间。此命名空间提供封装了很多运行时概念的类,例如程序集、模块、类型、方法、构造函数、字段和属性。

System.Reflection 类

语言组件 相应的 .NET 类
程序集 System.Reflection.Assembly
模块 System.Reflection.Module
抽象成员 System.Reflection.MemberInfo(以下所有的基类)
类型 System.Type
属性 System.Reflection.PropertyInfo
字段 System.Reflection.FieldInfo
事件 System.Reflection.EventInfo
抽象方法 System.Reflection.MethodBase(以下所有的基类)
方法 System.Reflection.MethodInfo
构造函数 System.Reflection.ConstructorInfo

反射层次结构

  1. 加载程序集(Assembly)
  2. 检索元数据(Type、PropertyInfo、FieldInfo、EventInfo、MethodInfo……)
  3. 动态创建对象/晚期绑定(System.Activator)
  4. 动态调用方法(Invoke)

查看元数据

元数据查看用到 System.Type类以及 System.Object.GetType() Type.GetType()typeof()方法

System.Object.GetType()返回了一个表示当前数据对象的Type类的实例,由示例可以看出,GetType()需要被一个实例调用,所以,静态类等无法实例化的对象 是无法使用 GetType()

 //只能获取到可以实例化的类
//静态类无法实例化,所以无法使用此方法获取
DateTime dt = new DateTime();
Type type = dt.GetType();
使用Type.GetType()根据类型的完全限定名同样可以获得元数据信息,采用这种方法, 我们不需要从提供该类型的编译时信息,只需要一个类型字符串名称。
//使用类型全名称字符串类获取
Type t = Type.GetType("System.Int32"); //用来获取静态类元数据
var type = Type.GetType("ConsoleApplication1.StaticClass");

使用typeof()操作符,我们不需要先建立一个实例来提取类型信息,但是,仍然需要知道 类型的编译时信息,因为typeof()需要的是类型的强类型名称,而不是文本表示。

//使用强类型名称来获取元数据信息
var type = typeof(DateTime); //StaticClass 为自定义的静态类
type = typeof(StaticClass);

  

使用上述任一方法都可以获取一个Type类实例,接着可以通过Type类提供的一系列方法类获取到相应的元数据信息,如字段、属性、方法等

Type类 

下面列举GetMethods()获取Int类下所有方法的示例,其他内容大致相同,不再罗列

            Type t = Type.GetType("System.Int32");
foreach (var method in t.GetMethods())
{
Console.WriteLine("->" + method.Name);
}

  

输出

此外,Type类提供了一些其他信息如:

  • IsAbstract:是否抽象
  • IsPublic:是否公共类
  • BaseType:基类信息
  • ……

关于Type类更多请戳:MSDN Type 类

加载程序集

有些场景我们需要在程序运行时以编程的方式载入程序集,即使在引用清单中引用此程序集, 这种按需加载外部程序集的操作被称为动态加载

System.Reflection定义了一个名为Assembly的类 使用这个类我们可以动态加载程序集,并找到关于程序集自身的属性。使用Assembly类型,我们还可以 动态加载私有或共享程序集,而且能够加载任意位置的程序集。使用Assembly类提供的方法Lode()LoadFrom()来完成上述操作.

LoadLoadFrom有众多的重载,所以,可以根据不同的场景使用不同的方法,都返回一个Assembly对象。

 public static Assembly Load(AssemblyName assemblyRef);
public static Assembly Load(byte[] rawAssembly);
public static Assembly Load(string assemblyString);
public static Assembly Load(AssemblyName assemblyRef, Evidence assemblySecurity);
public static Assembly Load(byte[] rawAssembly, byte[] rawSymbolStore);
public static Assembly Load(string assemblyString, Evidence assemblySecurity);
public static Assembly Load(byte[] rawAssembly, byte[] rawSymbolStore, Evidence securityEvidence);
public static Assembly Load(byte[] rawAssembly, byte[] rawSymbolStore, SecurityContextSource securityContextSource);
public static Assembly LoadFile(string path);
public static Assembly LoadFile(string path, Evidence securityEvidence);
public static Assembly LoadFrom(string assemblyFile);
public static Assembly LoadFrom(string assemblyFile, Evidence securityEvidence);
public static Assembly LoadFrom(string assemblyFile, byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm);
public static Assembly LoadFrom(string assemblyFile, Evidence securityEvidence, byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm);

  

Assembly.Load方法仅仅传入一个需要加载到内存的程序集的友好名称,因此,需要把希望反射的文件复制到当前文件Bin(bin\debug)目录下。 Assembly.LoadFrom方法根据文件的绝对路径来加载指定的文件。还可以指定一个网络路径,如果指定网络路径,则先下载该程序集,然后在加载。

    //这种方式需要把文件copy到同目录下bin/debug(Bin)下面
Assembly asm = Assembly.Load("Microsoft.ApplicationBlocks.Data");
Console.WriteLine(asm.FullName);
Type[] types = asm.GetTypes();
foreach (var item in types)
{
Console.WriteLine("Type:{0}", item);
}

  

  //DLL文件绝对路径 var assemblyFullPath = @"D:\Lab\Microsoft.ApplicationBlocks.Data.dll"; Assembly asm2 = Assembly.LoadFrom(assemblyFullPath); Console.WriteLine(asm2.FullName); foreach (var item in asm.GetTypes()) { Console.WriteLine("Type:{0}", item.Name); } 

晚期绑定

我们已经知道怎么获取加载一个程序集,获取程序集的元数据,那么,我们如何在运行时调用一个未添加在程序引用清单里的类型方法呢?答案是晚期绑定

晚期绑定(late binding)是一种创建一个给定类型的实例并在运行时调用其成员,而不需要在编译时知道它存在的一种技术。当建立一个晚期绑定到外部 程序集类型的应用程序时,因为没有设置该程序集的引用,因此,调用程序清单没有列出这个程序集。

System.Activator类(定义在mscorlib.dll)是晚期绑定的关键所在。我们只需关注Activator.CreateInstance() 方法,它用来建立一个晚期绑定类型的实例。同样CreateInstance()方法也是有多次重载,可以根据场景决定使用哪种方式。

        static void LateBinding()
{
var assemblyFullPath = @"D:\Lab\T.Entities.dll";
try
{
var asm = Assembly.LoadFrom(assemblyFullPath);
var types = asm.GetTypes();
var type = asm.GetType("T.Entities.Customers");
object obj = Activator.CreateInstance(type);
Console.WriteLine("Create a {0} using Late Binding", obj);
}
catch (Exception)
{
throw;
}
}

  

方法调用

我们已经加载一个没有添加引用的程序集,并且通过晚期绑定创建了其中某个成员一个实例, 接下来要做的就是要此实例为我们所用,但是,先思考一下,我们在上一个实例中,获取到的是一个object对象, 没有引用就没有办法转换成对应的对象,那我们就无法调用其方法!

解决这个问题我们还是要使用到反射,获取位置对象实例的元数据信息,比如MethodInfo,通过 MethodInfo.Invoke来实现方法的调用.

    static void InvokeNoParameterMethod()
{
var assemblyFullPath = @"D:\Lab\T.Entities.dll"; try
{
var asm = Assembly.LoadFrom(assemblyFullPath);
var type = asm.GetType("T.Entities.ModelHelper");
object obj = Activator.CreateInstance(type);
Console.WriteLine("Create a {0} using Late Binding", obj);
//"GetName"为ModelHelper类中的一个方法,返回一个姓名的字符串
MethodInfo mi = type.GetMethod("GetName");
mi.Invoke(obj, null);
string returnStr = mi.Invoke(obj, null) as string;
Console.WriteLine(returnStr);
}
catch (MissingMethodException ex)
{ throw;
}
}

  

反射、晚期绑定和自定义特性的使用背景

原书中提到一个场景 产品必须可以通过使用第三方工具进行扩展,以第三方软件提供商扩展Visual Studio 2010 IDE为例 解决这个问题的一个可能的方法:

  • 首先,可扩展的应用程序必须提供一些输入手段,允许用户指定被插入的模块,这需要动态加载
  • 其次,为了插入到环境中,可扩展的应用程序必须要确定模块是否支持正确的功能,这需要反射
  • 最后,可扩展的应用程序必须获取一个需要的基础架构的引用并调用成员触发底层功能,这经常需要晚期绑定

另外,我遇到的一些情况

  • ORM框架经常用到,比如属性自动赋值
  • AddIn模式开发
  • 抽象工厂模式中,动态生成工厂对象
  • ……

实际应用中,并不是所有的场景都需要使用反射,另外普遍观点认为反射会影响到性能,所以没有必要为了显示 你的技术多牛而非要牵扯个反射来。但是作为一种技术手段,了解才会想到运用, 可能会滥用,然后才会逐渐明白其妙处,最后为我所用。

参考资料

14C#4.0本质论(第3版)14C#与.NET 4 高级程序设计(第5版)14C#高级编程(第7版)

.NET之反射(1)的更多相关文章

  1. 隐私泄露杀手锏 —— Flash 权限反射

    [简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...

  2. Java学习之反射机制及应用场景

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...

  3. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...

  6. .NET面试题系列[6] - 反射

    反射 - 定义,实例与优化 在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等).如果答得好,还可能会 ...

  7. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  8. C++的性能C#的产能?! - .Net Native 系列五:.Net Native与反射

    此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题.本文是第五个主题:.Net Native与反射. 向导文链接:<C++的性能C#的产能?! - .Net Native 系列 ...

  9. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...

  10. SI与EMI(一) - 反射是怎样影响EMI

    Mark为期两天的EMC培训中大概分成四个时间差不多的部分,简单来说分别是SI.PI.回流.屏蔽.而在信号完整性的书籍中,也会把信号完整性分为:1.信号自身传输的问题(反射,损耗):2.信号与信号之间 ...

随机推荐

  1. pureftpd的搭建

    1,安装purefptd软件 ftp(file transfer protocol) 文件传输协议 cd  /usr/local/src  wget https://download.pureftpd ...

  2. 【TFS】增加组员,以及用户权限分配

    一.创建windows用户. 二.TFS ->组成员资格->双击 项目集合管理员->添加创建的Windows用户(最高权限) 三.设置权限: TFS权限的复杂,其实也不是很复杂,它只 ...

  3. jquery中this与$this的区别

    来源:http://www.jb51.net/article/19738.htm jQuery中this与$(this)的区别 $("#textbox").hover( funct ...

  4. JAVA多线程下,获取递增的序列号

    场景描述: 1,目前我们的系统可以简单归纳成MVC的架构模式 2,每个前端的请求过来,都会在C层开启事务,最后处理结束后,也在在C层关闭事务(实际是在C层的底层统一做了事务的开启和提交):      ...

  5. Android icons集合

    Android icons集合: Be aware that the style changes occur fairly regularly with each major release, so ...

  6. JavaScript 字符串常用操作纪要

    JavaScript 字符串用于存储和处理文本.因此在编写 JS 代码之时她总如影随形,在你处理用户的输入数据的时候,在读取或设置 DOM 对象的属性时,在操作 Cookie 时,在转换各种不同 Da ...

  7. XML的命名空间

    XML命名空间提供避免元素命名冲突的方法. 命名冲突:在XML中,元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突. 这个XML文档携带着某个表格中的信息: <ta ...

  8. EF6.0 自定义Code First约定

    自定义Code First约定有三种方式,分别是:Lightweight Conventions(轻量级约定).Configuration Conventions(配置型约定).Model-based ...

  9. 可以供MFC调用的,QT实现的DLL(qtwinmigrate实现)

    MFC和QT的消息循环机制不同,所以,要让QT写的DLL可以供MFC调用,要做一点特殊的处理 #include <qmfcapp.h> #include <qwinwidget.h& ...

  10. 无法关闭的QT程序——思路开阔一下,原来这么简单!

    做一个无法关闭的QT程序(想关闭时要在任务管理器里关闭),看似很难, 其实它并不难,只要让程序在关闭时启动它自身就可以了. 上代码: #include <QtGui> class Temp ...