专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »DotNet » 反射机制:全面学习.NET中的反射机制 »正文

反射机制:全面学习.NET中的反射机制

来源: 发布时间:星期四, 2009年2月12日 浏览:83次 评论:0


  清晰组件化目标是否因在库间共享过多类型信息而落空?或许您需要高效强类型化数据存储但如果每次对象模型发展后都需要更新您数据库架构那会耗费很大成本所以您更愿意在运行时推断出其类型架构吗?您需要交付能接受任意用户对象组件并以某种智能化方式处理它们吗?您希望库调方者能以编程方式向您介绍说明它们类型吗?

  如果您发现自己在苦苦维持强类型化数据结构同时又冀望于最大化运行时灵活性那么您大概会愿意考虑反射以及它如何改善您软件Software在本专栏中我将探讨 Microsoft? .NET Framework 中 .Reflection 命名空间以及它如何为您开发体验提供助益我将从些简单举例开始最后将讲述如何处理现实世界中序列化情形在此过程中我会展示反射和 CodeDom 如何配合工作以有效处理运行时数据

  在深入探究 .Reflection 的前我想先讨论反射编程首先反射可定义为由个编程系统提供任何功能此功能使员可以在无需提前了解其标识或正式结构情况下检查和操作代码实体这部分内容很多我将逐展开介绍说明

  首先反射提供了什么呢?您能用它做些什么呢?我倾向于将典型以反射为中心任务分为两类:检查和操作检查需要分析对象和类型以收集有关其定义和行为结构化信息除了些基本规定的外通常这是在事先不了解它们情况下进行(例如在 .NET Framework 中任何东西都继承自 .Object并且个对象类型引用通常是反射般起点)

  操作利用通过检查收集到信息动态地代码创建已发现类型新例子或者甚至可以轻松地动态重新结构化类型和对象需要指出个要点是对于大多数系统在运行时操作类型和对象较的在源代码中静态地进行同等操作会导致性能降低由于反射动态特性因此这是个必要取舍不过有很多窍门技巧和最佳做法可以优化反射性能(有关优化使用反射更多深入信息请参见 msdn.microsoft.com/msdnmag/issues/05/07/Reflection)

  那么什么是反射目标呢?员实际检查和操作什么呢?在我对反射定义中我用了“代码实体”这个新术语以强调个事实:从角度来说反射技术有时会使传统对象和类型的间界限变得模糊例如个典型以反射为中心任务可能是:

从对象 O 句柄开始并使用反射获得其相关定义(类型 T)句柄
检查类型 T获得它思路方法 M 句柄
个对象 O’(同样是类型 T)思路方法 M

  请注意我在从个例子穿梭到它底层类型从这类型到个思路方法的后又使用此思路方法句柄在另个例子上它 — 显然这是在源代码中使用传统 C# 编程技术无法实现在下文中探讨 .NET Framework .Reflection 的后我会再次通过个具体例子来解释这情形

  某些编程语言本身可以通过语法提供反射而另些平台和框架(如 .NET Framework)则将其作为系统库不管以何种方式提供反射在给定情形下使用反射技术可能性相当复杂编程系统提供反射能力取决于诸多原因:员很好地利用了编程语言功能表达了他概念吗?编译器是否在输出中嵌入足够结构化信息(元数据)以方便日后解读?有没有个运行时子系统或主机解释器来消化这些元数据?平台库是否以对员有用方式展示此解释结果?[Page]

  如果您头脑中想象个复杂、面向对象类型系统但在代码中却表现为简单、C 语言风格而且没有正式数据结构那么显然您不可能动态地推断出某变量 v1 指针指向某种类型 T 对象例子毕竟类型 T 是您头脑中概念它从未在您编程语句中明确地出现但如果您使用种更为灵活面向对象语言(如 C#)来表达抽象结构并直接引入类型 T 概念那么编译器就会把您想法转换成某种日后可以通过合适逻辑来理解形式就象公共语言运行时 (CLR) 或某种动态语言解释器所提供

  反射完全是动态、运行时技术吗?简单不是这样整个开发和执行周期中很多时候反射对开发人员都可用且有用些编程语言通过独立编译器实现这些编译器将高级代码直接转换成机器能够识别指令输出文件只包括编译过输入并且运行时没有用于接受不透明对象并动态分析其定义支持逻辑这正是许多传统 C 编译器情形在目标可执行文件中几乎没有支持逻辑因此您无法完成太多动态反射然而编译器会不时提供静态反射 — 例如普遍运用 typeof 运算符允许员在编译时检查类型标识

  另种完全区别情况是解释性编程语言总是通过主进程获得执行(脚本语言通常属于此类)由于完整定义是可用(作为输入源代码)并跟完整语言实现结合在起(作为解释器本身)因此所有支持自我分析所需技术都到位了这种动态语言频繁地提供全面反射功能以及组用于动态分析和操作丰富工具

  .NET Framework CLR 和它承载语言如 C# 属于中间形态编译器用来把源代码转换成 IL 和元数据后者和源代码相比虽属于较低级别或者较低“逻辑性”但仍然保留了很多抽象结构和类型信息旦 CLR 启动和承载了此基类库 (BCL) .Reflection 库便可以使用此信息并返回有关对象类型、类型成员、成员签名等信息此外它也可以支持包括后期绑定

  .NET 中反射

  要在用 .NET Framework 编程时利用反射您可以使用 .Reflection 命名空间此命名空间提供封装了很多运行时概念例如集、模块、类型、思路方法、构造、字段和属性图 1 中表显示.Reflection 中类如何和概念上运行时对应项对应起来

  尽管很重要不过 .Reflection.Assembly 和 .Reflection.Module 主要用于定位新代码并将其加载到运行时本专栏中我暂不讨论这些部分并且假定所有相关代码都已经加载

  要检查和操作已加载代码典型模式主要是 .Type通常您从获得个所关注运行时类别 .Type 例子开始(通过 Object.GetType)接着您可以使用 .Type 各种思路方法.Reflection 中探索类型定义并获得其它类例子例如如果您对某特定思路方法感兴趣并希望获得此思路方法.Reflection.MethodInfo 例子(可能通过 Type.GetMethod)同样如果您对某字段感兴趣并希望获得此字段.Reflection.FieldInfo 例子(可能通过 Type.GetField)[Page]



  旦获得所有必要反射例子对象即可根据需要遵循检查或操作步骤继续检查时您在反射类中使用各种描述性属性获得您需要信息(这是通用类型吗?这是例子思路方法吗?)操作时您可以动态地并执行思路方法通过构造创建新对象等等

  检查类型和成员

  让我们跳转到些代码中探索如何运用基本反射进行检查我将集中讨论类型分析个对象开始我将检索它类型而后考察几个有意思成员(请参见图 2)

  首先需要注意在类定义中乍看起来介绍说明思路方法篇幅比我预期要多很多这些额外思路方法是从哪里来呢?任何精通 .NET Framework 对象层次结构都会识别从通用基类 Object 自身继承这些思路方法(事实上我首先使用了 Object.GetType 检索其类型)此外您可以看到属性 getter 现在如果您只需要 MyClass 自身显式定义该如何办呢?换句话说您如何隐藏继承?或者您可能只需要显式定义例子

  随便在线看看 MSDN?就会发现大家都愿意使用 GetMethods 第 2个重载思路方法它接受 BindingFlags 参数通过结合来自 BindingFlags 枚举中区别您可以让仅返回所需思路方法子集替换 GetMethods 代的以:

GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |
BindingFlags.Public)

  结果是您得到以下输出(注意这里不存在静态帮助器和继承自 .Object )

Reflection Demo Example 1
Type Name: MyClass
Method Name: MyMethod1
Method Name: MyMethod2
Method Name: get_MyProperty
Property Name: MyProperty

  如果您事先知道类型名称(完全限定)和成员又该如何?您如何完成从枚举类型向检索类型转换?有了前两个举例中代码您已经有了能够实现基元类浏览器基本组件通过名称您可以找到个运行时实体然后枚举其各种相关属性

  动态代码

  迄今为止我已经获得运行时对象句柄(如类型和思路方法)仅作描述用例如输出它们名称但是如何做得更多呢?如何实际某个思路方法呢?

  此例几个要点是:首先个 MyClass, mc1 例子检索.Type 例子然后从该类型检索个 MethodInfo 例子最后 MethodInfo 时通过把它作为个参数来传递将其绑定到另个 MyClass (mc2) 例子中

  前面讲过对于您预期在源代码中见到类型和对象使用的间区别这个举例使这种区别变得模糊逻辑上您检索了个思路方法句柄然后该思路方法就象它属于个区别对象对于熟悉式编程语言员来说这可能轻而易举;但对于只熟悉 C# 员来说要分离对象实现和对象例子化可能就不是那么直观了

  组合在

  至此我已经探讨过检查和基本原理接下来我会用具体例子把它们组合在设想您希望交付个库带有必须处理对象静态帮助器但在设计时候您对这些对象类型没有任何概念!这要看指示看他希望如何从这些对象中提取有意义信息将接受个对象集合个思路方法串描述符然后它将遍历该集合每个对象思路方法聚合返回值(请参见图 5)[Page]

  就此例而言我要声明些约束条件首先串参数描述思路方法(必须由每个对象底层类型实现)不会接受任何参数并将返回个整数代码将遍历对象集合指定思路方法逐步计算出所有值平均值最后这不是生产代码在求和时候我不用担心参数验证或整数溢出

  在浏览举例代码时可以看到主和静态帮助器 ComputeAverage 的间协议除了对象自身通用基类的外并不依赖任何类型信息换句话说您可以彻底改变正在传送对象类型和结构但只要总是能使用串描述个思路方法且该思路方法返回整数ComputeAverage 就可以正常工作!

  需要注意个关键问题跟隐藏在最后这个例子中 MethodInfo(般反射)有关注意在 ComputeAverage foreach 循环中代码只从集合中个对象中抓取个 MethodInfo然后绑定用于所有后续对象正如编码所示它运行良好 — 这是 MethodInfo 缓存Cache个简单例子但此处有个根本性局限MethodInfo 例子仅能由其检索对象同等层级类型例子传入了 IntReturner 和 SonOfIntReturner(继承自 IntReturner)例子才能这样运行

  在举例代码中已经包含了名为 EnemyOfIntReturner 它实现了和其他两个类相同基本协议但并没有共享任何常见共享类型换句话说该接口逻辑上等同但在类型层级上没有重叠要探讨 MethodInfo 在该情形下使用请尝试向集合添加其他对象通过“ EnemyOfIntReturner(10)”得到个例子再次运行举例您会遇到个异常指出 MethodInfo 不能用于指定对象它和获得 MethodInfo 时原始类型完全无关(即使思路方法名称和基本协议是等同)要使您代码达到生产水准您需要做好遇到这情形准备

  个可能解决方案可以是通过自己分析所有传入对象类型保留对其共享类型层级(如果有)解释如果下对象类型和任意已知类型层级相异就需要获取和存储个新 MethodInfo解决方案是捕获 TargetException并重新获取个 MethodInfo 例子这里提到两种解决方案都各有其优缺点Joel Pobar 为本杂志 2007 5月期写过篇优秀文章内容有关 MethodInfo 缓冲和我所极力推荐反射性能

  希望此举例演示向应用或框架中添加反射可以为日后自定义或可扩展性增加更多灵活性不可否认较的本机编程语言中同等逻辑使用反射可能会有些繁琐如果您感到对您或您客户来说向代码中添加基于反射后期绑定过于麻烦(毕竟他们需要以某种方式在您框架中介绍说明他们类型和代码)那么可能仅需要适度灵活性以取得某种平衡

  序列化高效类型处理

  至此我们已通过若干举例讲述了 .NET 反射基本原理接下来让我们看下现实世界中情形如果您软件Software通过 Web 服务或其他进程外远程技术和其他系统进行交互那么您很可能已经遇到序列化问题序列化本质上是将活动、占用内存对象转变成适合线上传输或磁盘存储数据格式



  .NET Framework 中 .Xml.Serialization 命名空间提供了拥有 XmlSerializer 强大序列化引擎它可以使用任意托管对象并将其转换成 XML(日后也可将 XML 数据转换回类型化对象例子过程称的为反序列化)XmlSerializer 类是种强大、企业就绪软件Software片断如果您在项目中面临序列化问题它将是您首选但为了教学目我们来探讨如何实现序列化(或者其他类似运行时类型处理例子)[Page]

  设想情形:您正在交付个框架需要使用任意用户类型对象例子并将其转换成某种智能型数据格式例如假定有个驻留内存对象类型为如下所示 Address:

(pseudocode)
Address
{
AddressID id;
String Street, City;
StateType State;
ZipCodeType ZipCode;
}

  如何生成适当数据表示形式以方便日后使用?或许个简单文本呈现将解决这问题:

Address: 123
Street: 1 Microsoft Way
City: Redmond
State: WA
Zip: 98052

  如果事先完全了解需要转换正式数据类型(例如自己编写代码时)事情就变得非常简单:

foreach(Address a in AddressList)
{
Console.WriteLine(“Address:{0}”, a.ID);
Console.WriteLine(“\\tStreet:{0}”, a.Street);
... // and so _disibledevent=>SimpleDataWriter writer = SimpleDataWriter;
mySerializer.Serialize(addressInstance, writer);

  结束

  强烈建议您亲自试用下举例代码尤其是 SimpleSerialization 库我在 SimpleSerializer 些有趣部分都添加了注释希望能够有所帮助当然如果您需要在产品代码中进行严格序列化那么确实要依靠 .NET Framework 中提供技术(例如 XmlSerializer)但如果您发现在运行时需要使用任意类型并能高效处理它们我希望您采用我 SimpleSerialization 库作为自己方案

0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: