论坛首页 编程语言技术论坛

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

浏览 1566 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-09-23   最后修改:2009-09-23
如果你试用(没错字,我就是说“试用”而不是“使用”)过System.Reflection.Emit的功能,可能会觉得这帖标题很怪——Builder系列跟对应的Info系列不是同根生么,前者继承对应的后者:
Builder系列(派生类) Info系列(基类)
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
System.Reflection.Emit.EventBuilder System.Object

(最后一个不是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是同一个对象,自然就可以用于反射了。

先前跟老赵在twitter上的对话:
@rednaxelafx 写道
@jeffz_cn TypeBuilder不能直接用于反射,FieldBuilder不能用于Get/SetValue,MethodBuilder不能直接用于Invoke……一切都是因为metadata token……

@jeffz_cn 写道
@rednaxelafx FieldBuilder可以用来Stfld的,我的Eazy里刚用过,呵呵。你可以去获取代码看看,https://eazy.svn.codeplex.com/svn 在TypeBuilderExtensions.cs的79行。

老赵误解了我想说的“不能用于反射”的意思。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系列类型的实例了。
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics