反射发出

nd | | 访问(114)

  "反射"和"反射发出(Emit)"的关系

  相信反射大家都不陌生,我也曾写过关于反射的文章,大家有兴趣可以看看,但是今天要说的不是"反射"而是"反射发出(Emit)"。我们知道反射的主要功能是获得对象的信息、调用对象的方法等;而反射发出的主要功能是动态的创建对象。那么是不是二者就没有关系呢?事实上二者不经有关系而且关系十分密切。从命名空间我们就可以看出来,反射处于System.Reflection 命名空间下,而反射发出Emit处于System.Reflection.Emit 命名空间下,它的功能虽然主要是动态创建对象,但是很多反射能够完成的工作它也可以完成而且速度更快(实现当然较反射而言要麻烦一些)。也就是说反射主要用到对象已经存在的情况下,而反射发出主要用到对象并不存在等情况下(而利用代码动态的构建对象)。

  "反射发出(Emit)"动态构建对象的原理

  我们既然知道了Emit可以动态创建对象,那么Emit是如何做到呢?这就必须要提到MSIL,它是类似于java虚拟机的一种无关于CPU的中间语言,也就是说不管你是用什么语言只要最终生成IL,那么.Net就可以执行(这也是.Net上为什么能够运行C#、VB、F#等多种语言的原因)。利用Emit之所以能够动态构建对象,也是因为它可以直接生成IL,这样一来我们当然也就可以动态创建对象了,而且速度几乎接近于直接运行已有代码。

  "反射发出(Emit)"的用途

  Emit最典型的应用就是:增强反射性能、动态代理(AOP)、ORM实现等。

  "反射发出(Emit)"的实现

  使用Emit的步骤几乎是固定的,网上很多文章都列举的很清楚,这里我们就先看代码吧。假设现在我准备构建一个这样的对象:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyEmit
{
    public class MyClass
    {
        private double a = 0;
        private double b = 0;
        public MyClass()
        {
        }
        public MyClass(double a,double b)
        {
            this.a = a;
            this.b = b;
        }
        
        public double Sum()
        {
            return a + b;
        }
        public double A
        {
            get
            {
                return a;
            }
            set
            {
                a = value;
            }
        }
    }
}

  上面的代码我们直接书写然后在vs中编译几乎是再简单不过的事情了,可是它包括了成员变量、构造函数、属性、方法等多个对象常用元素,最为一个例子应该说是比较有代表性的了。下面就来看看我们如何使用代码动态构建来代替手工书写编译的过程吧:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace Emit_ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            //构建程序集
            AssemblyName aName = new AssemblyName("MyEmit");
            AppDomain aDomain = AppDomain.CurrentDomain;//应用程序域,这里设为当前域
            AssemblyBuilder aBuidler = aDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
            //定义模块
            ModuleBuilder mBuidler = aBuidler.DefineDynamicModule("MyModule");
            //创建类型(其实就是一个类)
            TypeBuilder tBuidler = mBuidler.DefineType("MyEmit.MyClass", TypeAttributes.Public | TypeAttributes.Class);
            //--实现类--//
            //定义两个私有字段
            FieldBuilder fb_a = tBuidler.DefineField("a", typeof(double), FieldAttributes.Private);
            FieldBuilder fb_b = tBuidler.DefineField("b", typeof(double), FieldAttributes.Private);
            //为私有变量赋值
            fb_a.SetConstant((double)0);
            fb_b.SetConstant((double)0);
            //定义构造函数
            ConstructorBuilder ctstBuilderWithoutParameters = tBuidler.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, null);//无参数构造函数
            ILGenerator ctstWithoutParametersGenerator = ctstBuilderWithoutParameters.GetILGenerator();
            ctstWithoutParametersGenerator.Emit(OpCodes.Ret);
            ConstructorBuilder ctstBuilder = tBuidler.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[] { typeof(double), typeof(double) });
            ILGenerator ctstGenerator=ctstBuilder.GetILGenerator();
            ctstGenerator.Emit(OpCodes.Ldarg_0);
            ctstGenerator.Emit(OpCodes.Ldarg_1);
            ctstGenerator.Emit(OpCodes.Stfld, fb_a);//给字段赋值
            ctstGenerator.Emit(OpCodes.Ldarg_0);
            ctstGenerator.Emit(OpCodes.Ldarg_2);
            ctstGenerator.Emit(OpCodes.Stfld, fb_b);
            
            ctstGenerator.Emit(OpCodes.Ret);
            //定义属性
            PropertyBuilder pro_A = tBuidler.DefineProperty("A", PropertyAttributes.None, typeof(double), null);
            //属性的get方法
            MethodBuilder mBuidlerGetA = tBuidler.DefineMethod("get", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(double), Type.EmptyTypes);
            ILGenerator getAGenerator = mBuidlerGetA.GetILGenerator();
            getAGenerator.Emit(OpCodes.Ldarg_0);
            getAGenerator.Emit(OpCodes.Ldfld,fb_a);
            getAGenerator.Emit(OpCodes.Ret);
            MethodBuilder mBuidlerSetA = tBuidler.DefineMethod("set", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new Type[]{typeof(double)});
            ILGenerator setAGenerator = mBuidlerSetA.GetILGenerator();
            setAGenerator.Emit(OpCodes.Ldarg_0);
            setAGenerator.Emit(OpCodes.Ldarg_1);
            setAGenerator.Emit(OpCodes.Ldfld, fb_a);
            setAGenerator.Emit(OpCodes.Ret);
            //定义方法
            MethodBuilder mtdBuidler = tBuidler.DefineMethod("Sum",MethodAttributes.Public,typeof(double),null);
            ILGenerator mtdGenerator = mtdBuidler.GetILGenerator();
            mtdGenerator.Emit(OpCodes.Ldarg_0);
            mtdGenerator.Emit(OpCodes.Ldfld,fb_a);
            mtdGenerator.Emit(OpCodes.Ldarg_0);
            mtdGenerator.Emit(OpCodes.Ldfld,fb_b);
            mtdGenerator.Emit(OpCodes.Add);
            mtdGenerator.Emit(OpCodes.Ret);
           
            //创建引用、调用方法
            Type MyClass = tBuidler.CreateType();
            object myInstance = Activator.CreateInstance(MyClass,new object[]{1.1,2.2});//实例化
            //object result = MyClass.InvokeMember("Add", BindingFlags.InvokeMethod, null, myInstance, new object[]{1.2,2.3});
            object result = MyClass.GetMethod("Sum").Invoke(myInstance,null);
            Console.WriteLine("this result is {0}", result.ToString());
        }
    }
}

  上面的注释已经说明了使用Emit的常用步骤,一步步书写就可以了,但是关键就是方法实现部分的代码不是太容易,但我们上面已经说过使用Emit的过程可以看成是构建IL的过程,如果我们能够看到我们想要构建的对象的IL代码再用Emit来书写就容易多了。这里就向大家推荐一个工具—reflector,我们可以先将上面MyEmit.MyClass这个类编译成dll或exe,然后使用reflector查看器相应的IL代码,然后在书写Emit的时候(主要是方法体的实现)对照IL来书写就容易多了。例如上面Sum方法的IL代码:

.method public hidebysig instance float64 Sum() cil managed
{
    .maxstack 2
    .locals init (
        [0] float64 CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldfld float64 MyEmit.MyClass::a
    L_0007: ldarg.0 
    L_0008: ldfld float64 MyEmit.MyClass::b
    L_000d: add 
    L_000e: stloc.0 
    L_000f: br.s L_0011
    L_0011: ldloc.0 
    L_0012: ret 
}

  当然,reflector反编译过的IL可以帮助我们,但是只能够作为参考,中间需要增减相关代码,如果想要彻底的了解Emit中IL的书写还需要多加学习。另外,上面我们的程序集、模块、构造函数、属性、方法等的创建都是使用Emit来实现的,但是最后对于类的实例化和方法的调用却是典型的反射应用,当然这些我们仍然可以使用Emit来做到,而且网上也有很多的介绍,如果今后有时间我会再和大家一块学习的,今天就先到这里吧。

  文章来源:http://www.cnblogs.com/kenshincui/archive/2010/11/25/1887571.html