SRE里的Builder系列到Info系列的转换

本文探讨了System.Reflection.Emit命名空间中的Builder系列类如何正确应用于反射操作。Builder类并非直接的反射工具,而是需要通过特定方法转换成Info系列类才能进行有效的反射操作。
如果你试用(没错字,我就是说“试用”而不是“使用”)过[url=http://msdn.microsoft.com/en-us/library/system.reflection.emit.aspx]System.Reflection.Emit[/url]的功能,可能会觉得这帖标题很怪——Builder系列跟对应的Info系列不是同根生么,前者继承对应的后者:
[table]
| [b]Builder系列(派生类)[/b] | [b]Info系列(基类)[/b] |
| System.Reflection.Emit.AssemblyBuilder | System.Reflection.Assembly |
| System.Reflection.Emit.ModuleBuilder | System.Reflection.Module |
| System.Reflection.Emit.TypeBuilder | System.Type |
| System.Reflection.Emit.EnumBuilder | System.Type |
| System.Reflection.Emit.GenericTypeParameterBuilder | System.Type |
| System.Reflection.Emit.FieldBuilder | System.Reflection.FieldInfo |
| System.Reflection.Emit.MethodBuilder | System.Reflection.MethodInfo |
| System.Reflection.Emit.ConstructorBuilder | System.Reflection.ConstructorInfo |
| System.Reflection.Emit.PropertyBuilder | System.Reflection.PropertyInfo |
| [color=gray]System.Reflection.Emit.EventBuilder[/color] | [color=gray]System.Object[/color] |
[/table]
(最后一个不是Info系列的,所以用灰色表示了。前面的Assembly、Module和Type都算在“广义的Info系列”里)

那么通过SRE创建出一个类型之后,手上的Builder系列类型可以直接用于反射吗?
答案是:不行,Builder系列唯一的使命就是Emit,没有别的用途。
看个例子就明白:demo.cs
using System;
using System.Reflection;
using System.Reflection.Emit;

static class Demo {
static void Main(string[] args) {
var assemblyName = new AssemblyName("DemoAssembly");
var assemblyBuilder = AppDomain.CurrentDomain
.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(
"DemoAssembly" );
var typeBuilder = moduleBuilder.DefineType(
"TestType00", TypeAttributes.Public);
var fieldBuilder = typeBuilder.DefineField(
"x", typeof(int), FieldAttributes.Private);
var testType = typeBuilder.CreateType();

// get a FieldInfo through normal reflection
var fieldInfo = testType.GetField(
"x", BindingFlags.NonPublic | BindingFlags.Instance);

// create new instance of TestType00
var instance = Activator.CreateInstance(testType);
// set the field x through normal reflection
fieldInfo.SetValue(instance, 2);
try {
// try to get the field x through FieldBuilder
// a FieldBuilder is a FieldInfo, but this won't work...
Console.WriteLine(fieldBuilder.GetValue(instance));
} catch (Exception e) {
Console.WriteLine(e);
//System.NotSupportedException: The invoked member is not supported in a dynamic module.
// at System.Reflection.Emit.FieldBuilder.GetValue(Object obj)
// at Demo.Main(String[] args) in d:\demo.cs:line 29
}

// get a FieldInfo from a FieldBuilder
var fieldInfoFromResolved = moduleBuilder.ResolveField(
moduleBuilder.GetFieldToken(fieldBuilder).Token);
// WARNING: can't call resolve before calling CreateType,
// because resolving a type will load the type; an unfinished
// type cannot be loaded
Console.WriteLine(fieldInfo == fieldInfoFromResolved); // true

// get the field x through converted FieldInfo
Console.WriteLine(fieldInfoFromResolved.GetValue(instance)); // 2

// set the field x through converted FieldInfo
fieldInfoFromResolved.SetValue(instance, 5);
// get the field x through normal reflection
Console.WriteLine(fieldInfo.GetValue(instance)); // 5
}
}

稍微解释一下这段代码:
开头的部分都是很常见的SRE boilerplate,先得到AssemblyBuilder,然后ModuleBuilder,然后TypeBuilder。接下来定义了一个域,得到一个FieldBuilder。如开头所说,FieldBuilder继承FieldInfo,那么它似乎也应该可以用于反射?马上try一下,却得到NotSupportedException。此路不通。
接下来的部分就有趣了。FieldBuilder虽然不能用于反射,但它仍然持有正确的metadata token;而从metadata就可以找出“真正”的FieldInfo。代码中第38的调用完成了这个工作。可以看到,这个方法得到的FieldInfo与通过正常反射得到的FieldInfo是同一个对象,自然就可以用于反射了。

先前跟[url=http://jeffreyzhao.cnblogs.com/]老赵[/url]在twitter上的对话:
[quote="@rednaxelafx"]@jeffz_cn TypeBuilder不能直接用于反射,FieldBuilder不能用于Get/SetValue,MethodBuilder不能直接用于Invoke……一切都是因为metadata token……[/quote]
[quote="@jeffz_cn"]@rednaxelafx FieldBuilder可以用来Stfld的,我的Eazy里刚用过,呵呵。你可以去获取代码看看,[url]https://eazy.svn.codeplex.com/svn[/url] 在TypeBuilderExtensions.cs的79行。[/quote]
老赵误解了我想说的“不能用于反射”的意思。Builder系列类型当然可以用于在生成IL时使用,因为那就是它们的本职工作——Emit。生成的IL在执行的时候并不会使用Builder系列类型的实例通过反射去取值/设值/调用等,当然没问题。一旦想把Builder系列直接用于Emit之外的用途,就会遇到NotSupportedException。

上面提到的转换,再举几个例子:
var type =
moduleBuilder.ResolveType(moduleBuilder.GetTypeToken(typeBuilder).Token);
var fieldInfo =
moduleBuilder.ResolveField(moduleBuilder.GetFieldToken(fieldBuilder).Token);
var methodInfo =
moduleBuilder.ResolveMethod(moduleBuilder.GetMethodToken(methodBuilder).Token);

Metadata是以module为单位组织的,token只在module内部有效。所以ResolveXXX系列的方法也都是在ModuleBuilder上。

这种通过token去获取用于反射的Info系列类型的方法,比通过正常的反射API更高效易用。所以如果是刚动态创建出类型,手上还有Builder系列类型的实例的引用,就不必绕圈通过正常反射获取Info系列类型的实例了。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值