动态反射
在我们之前的文章,反射的第一部分:发现和执行里,我们已经介绍了System.Reflection命名空间及其包含的类,开发人员使用这些类可以查看程序集的元数据,并且可以在运行时查找和发现类型,甚至可以调用发现的代码。在这篇文章中,我们将探讨反射的高级功能:Emit,它具有在运行时动态的产生代码的功效。
1. 创建一个新的程序集(程序集是动态的存在于内存中或把它们持久化到磁盘上)。 2. 在程序集内部,创建一个模块(module)。 3. 在模块内部,创建一个类型。 4. 给类型添加属性和方法。 5. 产生属性和方法内部的代码 确切得说,当你使用Reflection.Emit类产生代码时,以上描述的是你实际中要遵循的过程。
a) 创建一个AssemblyName(用于唯一标识和命名程序集)。 b) 获取当前应用程序域的一个引用(使用应用程序域提供的方法,返回AssemblyBuilder对象)。 c) 通过调用AppDomain.DefineDynamicAssembly产生一个AssemblyBuilder对象实例。 为了开始程序集的构建过程,你首先需要创建一个AssemblyName实例,用于标识你的程序集。如下:
AssemblyName name = new AssemblyName();
name.Name = "MyAssembly";
接下来,你需要System.AppDomain类的一个实例。你可以从当前运行的线程实例中获取。
AppDomain ad = System.Threading.Thread.GetDomain();
这两个实例创建以后,你现在就可以定义一个AssemblyBuilder变量,然后使用之前创建的AssemblyName和AppDomain的实例把它实例化。AssemblyBuilder类是整个反射发出的工作支架。它给你,从零开始构造一个新的程序集提供了主要的机制。除此之外,你还需要指定一个AssemblyBuilderAccess枚举值,它将表明,你是想把程序集写入磁盘,保存到内存,还是两者都有。在这个例子里,你想把程序集保存在内存里。
AssemblyBuilder builder;
builder = ad.DefineDynamicAssembly(name,AssemblyBuilderAccess.Run);
第二步:定义一个模块(module)
ModuleBuilder mb;
mb = builder.DefineDynamicModule("MyModule");
第三步:创建一个类型
TypeBuilder theClass;
theClass = mb.DefineType("MathOps",TypeAttributes.Public & TypeAttributes.Class);
注意,你已经使用TypeAttributes枚举指定了该类型的可见度为公共的。
使用MethodBuilder类可以为你指定的类型定义方法。你可以在之前创建的类型对象上(theClass)调用DefineMethod获取一个MethodBuilder实例的引用。DefineMethod携带四个参数:方法的名称,方法可能的属性(如:public,private等等),方法的参数以及方法的返回值。在子程序里,参数和返回值可以是void值。对于这个例子里即将创建的方法,你同时需要指定参数和返回值的类型。 为了定义返回值的类型,创建一个包含返回类型值的类型对象(一个System.Int32类型的值)
//返回值
Type ret = typeof(System.Int32);
//参数
Type[] param = new Type[2]; param[0] = typeof(System.Int32); param[1] = typeof(System.Int32);
有了这些值,你现在就可以调用DefineMethod方法了。
MethodBuilder methodBuilder;
methodBuilder = theClass.DefineMethod("ReturnSum",MethodAttributes.Public,ret,param);
第五步:产生代码
有一点是需要注意的,反射发出(reflection emit)的类不能产生源代码。换句话说,这里的结果并不会产生Visual Basic.NET或者C#代码,而是产生MSIL op 代码。MSIL(微软中间语言)是一种接近于汇编程序的中间代码语言。当.NET JIT 编译器产生本地二进制代码的时候,就需要编译MSIL。Op代码是低级的,类似于汇编程序的操作指令。 考虑方法ReturnSum的如下实现:
public int ReturnSum(int val1,int val2)
{ return val1 + val2; }
如果你想“发出”这一段代码,你首先需要知道如何仅使用MSIL op代码编写这个方法。值得高兴的是,这里有一个快速,简单的办法可以做到。你可以简单的编译一下这段代码,然后使用.NET框架里的实用工具ildasm.exe查看程序集的结果。以下MSIL版本的代码是编译上面的方法产生的:
.method public hidebysig instance int32 ReturnSum(int32 val1,
int32 val2) cil managed { // 代码大小 8 (0x8) .maxstack 2 .locals init ([0] int32 CS$00000003$00000000) IL_0000: ldarg.1 IL_0001: ldarg.2 IL_0002: add IL_0003: stloc.0 IL_0004: br.s IL_0006 IL_0006: ldloc.0 IL_0007: ret } // end of method Class1::ReturnSum
ILGenerator gen = methodBuilder.GetILGenerator();
使用gen对象,你可以把op指令注入到你的方法里。
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldarg_2); gen.Emit(OpCodes.Add); gen.Emit(OpCodes.Stloc_0); gen.Emit(OpCodes.Br_S); gen.Emit(OpCodes.Ldloc_0); gen.Emit(OpCodes.Ret);
theClass.CreateType();
命名空间和类 正如你知道的那样,Reflection.Emit命名空间包含一系列核心“构建”类,它们用于创建类型和与新类型相关的,如:各种特性,方法,字段,属性等等。Table 1描述了使用反射产生代码用到的主要的类。 Table 1 反射发出相关类的参考
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |