如何根据IL动态编写带有Reflection.Emit的C#类

时间:2015-04-08 03:47:53

标签: c# reflection.emit il ilgenerator

假设我们有一个界面:

public interface ICalculator
{
    decimal Calculate(decimal x, decimal y);
}

计算逻辑在 javascript (实际上是TypeScript)代码中实现,我们希望使用Reflection.Emit动态创建以下实现,因此我们可以与C#实现共享单元测试: / p>

public class Calculator : ICalculator
{

    private ScriptEngine ScriptEngine;

    public Calculator(ScriptEngine scriptEngine, string jsFileFullPath)
    {
        this.ScriptEngine = scriptEngine;
        var jsFileContent = File.ReadAllText(jsFileFullPath);
        this.ScriptEngine.Execute(jsFileContent);
    }

    public decimal Calculate(decimal x, decimal y)
    {

        string script = @"
                var rf1013 = new TotalTaxation.TaxformCalculation.RF1013({0},{1});
                rf1013.Calculate();
                var result = rf1013.RF1013Sum;
            ";

        this.ScriptEngine.Evaluate(string.Format(script, x, y));

        var result = this.ScriptEngine.Evaluate("result");
        return Convert.ToDecimal(result);

    }
}

我们可以从IL DASM获得IL:

.class public auto ansi beforefieldinit Calculator
       extends [mscorlib]System.Object
       implements ICalculator
{
} // end of class Calculator


.field private class [ClearScript]Microsoft.ClearScript.ScriptEngine ScriptEngine

.method public hidebysig specialname rtspecialname 
        instance void  .ctor(class [ClearScript]Microsoft.ClearScript.ScriptEngine scriptEngine,
                             string jsFileFullPath) cil managed
{
  // Code size       37 (0x25)
  .maxstack  2
  .locals init ([0] string jsFileContent)
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0006:  nop
  IL_0007:  nop
  IL_0008:  ldarg.0
  IL_0009:  ldarg.1
  IL_000a:  stfld      class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine
  IL_000f:  ldarg.2
  IL_0010:  call       string [mscorlib]System.IO.File::ReadAllText(string)
  IL_0015:  stloc.0
  IL_0016:  ldarg.0
  IL_0017:  ldfld      class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine
  IL_001c:  ldloc.0
  IL_001d:  callvirt   instance void [ClearScript]Microsoft.ClearScript.ScriptEngine::Execute(string)
  IL_0022:  nop
  IL_0023:  nop
  IL_0024:  ret
} // end of method JsRF1013Wrapper::.ctor


.method public hidebysig newslot virtual final 
        instance valuetype [mscorlib]System.Decimal 
        Calculate(valuetype [mscorlib]System.Decimal x,
                  valuetype [mscorlib]System.Decimal y) cil managed
{
  // Code size       65 (0x41)
  .maxstack  4
  .locals init ([0] string script,
           [1] object result,
           [2] valuetype [mscorlib]System.Decimal CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "\r\n                    var rf1013 = new TotalTaxati"
  + "on.TaxformCalculation.RF1013({0},{1});\r\n                    rf1013.Calc"
  + "ulate();\r\n                    var result = rf1013.RF1013Sum;\r\n         "
  + "       "
  IL_0006:  stloc.0
  IL_0007:  ldarg.0
  IL_0008:  ldfld      class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine
  IL_000d:  ldloc.0
  IL_000e:  ldarg.1
  IL_000f:  box        [mscorlib]System.Decimal
  IL_0014:  ldarg.2
  IL_0015:  box        [mscorlib]System.Decimal
  IL_001a:  call       string [mscorlib]System.String::Format(string,
                                                              object,
                                                              object)
  IL_001f:  callvirt   instance object [ClearScript]Microsoft.ClearScript.ScriptEngine::Evaluate(string)
  IL_0024:  pop
  IL_0025:  ldarg.0
  IL_0026:  ldfld      class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine
  IL_002b:  ldstr      "result"
  IL_0030:  callvirt   instance object [ClearScript]Microsoft.ClearScript.ScriptEngine::Evaluate(string)
  IL_0035:  stloc.1
  IL_0036:  ldloc.1
  IL_0037:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Convert::ToDecimal(object)
  IL_003c:  stloc.2
  IL_003d:  br.s       IL_003f
  IL_003f:  ldloc.2
  IL_0040:  ret
} // end of method Calculator::Calculate

我们创建了TypeCreator来做到这一点:

namespace TypeCreator
{
    public interface ICalculator
    {
        decimal Calculate(decimal x, decimal y);
    }

    public class TypeCreator
    {
        private Type targetType;
        private ScriptEngine scriptEngine;
        private string jsFileFullPath;

        public TypeCreator(Type targetType, ScriptEngine scriptEngine, string jsFileFullPath)
        {
            this.targetType = targetType;
            this.scriptEngine = scriptEngine;
            this.jsFileFullPath = jsFileFullPath;
        }
        public Type build()
        {
            AppDomain currentAppDomain = AppDomain.CurrentDomain;
            AssemblyName assyName = new AssemblyName();
            assyName.Name = "MyAssyFor_" + targetType.Name;
            AssemblyBuilder assyBuilder = currentAppDomain.DefineDynamicAssembly(assyName, AssemblyBuilderAccess.Run);
            ModuleBuilder modBuilder = assyBuilder.DefineDynamicModule("MyModFor_" + targetType.Name);
            String newTypeName = "Imp_" + targetType.Name;
            TypeAttributes newTypeAttribute = TypeAttributes.Class | TypeAttributes.Public;

            Type[] ctorParams = new Type[] { typeof(ScriptEngine), typeof(string) };
        Type newTypeParent;
        Type[] newTypeInterfaces;
        if (targetType.IsInterface)
        {
            newTypeParent = null;
            newTypeInterfaces = new Type[] { targetType };
        }
        else
        {
            newTypeParent = targetType;
            newTypeInterfaces = new Type[0];
        }
        TypeBuilder typeBuilder = modBuilder.DefineType(newTypeName, newTypeAttribute, newTypeParent, newTypeInterfaces);

        FieldBuilder scriptEngineField = typeBuilder.DefineField("scriptEngine", typeof(ScriptEngine),
                                                           FieldAttributes.Public);
        FieldBuilder jsFileFullPathField = typeBuilder.DefineField("jsFileFullPath", typeof(string),
                                                           FieldAttributes.Public);
        Type objType = Type.GetType("System.Object");
        ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);

        ConstructorBuilder wrapperCtor = typeBuilder.DefineConstructor(
                       MethodAttributes.Public,
                       CallingConventions.Standard,
                       ctorParams);
        ILGenerator ctorIL = wrapperCtor.GetILGenerator();
        ctorIL.Emit(OpCodes.Ldarg_0);
        ctorIL.Emit(OpCodes.Call, objCtor);
        ctorIL.Emit(OpCodes.Nop);
        ctorIL.Emit(OpCodes.Nop);
        ctorIL.Emit(OpCodes.Ldarg_0);
        ctorIL.Emit(OpCodes.Ldarg_1);
        ctorIL.Emit(OpCodes.Stfld, scriptEngineField);
        ctorIL.Emit(OpCodes.Ldarg_2);
        ctorIL.Emit(OpCodes.Call, typeof(File).GetMethod("ReadAllText", new Type[] { typeof(string) }));
        ctorIL.Emit(OpCodes.Stloc_0);
        ctorIL.Emit(OpCodes.Ldarg_0);
        ctorIL.Emit(OpCodes.Ldfld, scriptEngineField);
        ctorIL.Emit(OpCodes.Ldloc_0);
        ctorIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) }));
        ctorIL.Emit(OpCodes.Nop);
        ctorIL.Emit(OpCodes.Nop);
        //ctorIL.Emit(OpCodes.Stfld, jsFileFullPathField);
        ctorIL.Emit(OpCodes.Ret);

        string methodName = "Calculate";

        MethodBuilder getFieldMethod = typeBuilder.DefineMethod(methodName, MethodAttributes.Public, typeof(decimal), new Type[] { typeof(decimal), typeof(decimal) });
        ILGenerator methodIL = getFieldMethod.GetILGenerator();
        methodIL.Emit(OpCodes.Nop);
        methodIL.Emit(OpCodes.Ldstr, @"var rf1013 = new TotalTaxation.TaxformCalculation.RF1013({0},{1});
                rf1013.Calculate();
                var result = rf1013.RF1013Sum;");
        methodIL.Emit(OpCodes.Stloc_0);
        methodIL.Emit(OpCodes.Ldarg_0);
        methodIL.Emit(OpCodes.Ldfld, scriptEngineField);
        methodIL.Emit(OpCodes.Ldloc_0);
        methodIL.Emit(OpCodes.Ldarg_1);
        methodIL.Emit(OpCodes.Box, typeof(decimal));
        methodIL.Emit(OpCodes.Ldarg_2);
        methodIL.Emit(OpCodes.Call, typeof(String).GetMethod("Format", new Type[] { typeof(string), typeof(object), typeof(object) }));

        methodIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) }));
        methodIL.Emit(OpCodes.Pop);
        methodIL.Emit(OpCodes.Ldarg_0);
        methodIL.Emit(OpCodes.Ldfld, scriptEngineField);
        methodIL.Emit(OpCodes.Ldstr, "result");
        methodIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) }));
        methodIL.Emit(OpCodes.Stloc_0);
        methodIL.Emit(OpCodes.Ldloc_0);
        methodIL.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToDecimal", new Type[] { typeof(object) }));
        methodIL.Emit(OpCodes.Stloc_2);
        methodIL.Emit(OpCodes.Br_S);
        methodIL.Emit(OpCodes.Ldloc_2);
        methodIL.Emit(OpCodes.Ret);

        return (typeBuilder.CreateType());
    }
}

像这样使用:

        var jsFileFullPath = "JsFiles\\Total.js";
        TypeCreator tc = new TypeCreator(typeof(ICalculator), new JScriptEngine(), jsFileFullPath);
        Type t = tc.build();

        // Prepares the parameters
        var scriptArgs = new System.Collections.ArrayList();
        scriptArgs.Add(new JScriptEngine());
        scriptArgs.Add(jsFileFullPath);

        ICalculator calculator = (ICalculator)Activator.CreateInstance(t, scriptArgs);
        var result = calculator.Calculate(3.0m, 5.0m);
        Console.Write(string.Format("calculator.Calculate(3.0m, 5.0m)={0}", result));
        Console.Read();

抛出异常:

  

方法&#39>计算'在类型' Imp_ICalculator'来自assembly' MyAssyFor_ICalculator,Version = 0.0.0.0,Culture = neutral,PublicKeyToken = null'没有实施。

问题是什么?

1 个答案:

答案 0 :(得分:1)

您的代码中存在几个问题:

  1. 报告的错误是因为实现接口的方法必须是虚拟的(参见§II.12.2 Implementing virtual methods on interfaces of ECMA-335)。从反汇编的IL中也可以看出这一点(我相信newslotfinal修饰符在那里,因此该方法的行为不像C#virtual方法):

    .method public hidebysig newslot virtual final 
            instance valuetype [mscorlib]System.Decimal 
            Calculate(valuetype [mscorlib]System.Decimal x,
                      valuetype [mscorlib]System.Decimal y) cil managed
    

    要解决此问题,您需要将| MethodAttributes.Virtual添加到DefineMethod()来电。

  2. 您使用一个Activator.CreateInstance()参数调用ArrayList。如果要使用两个参数调用构造函数,则需要传入单个object[]或使用params

    Activator.CreateInstance(t, new ScriptEngine(), jsFileFullPath)
    
  3. 您在IL中使用局部变量,但您没有声明它们。使用DeclareLocal()来解决此问题。

  4. 这是我停止的地方,因此您的代码可能存在其他问题。