如何使用 Reflection.Emit 注入文字表达式?

Posted

技术标签:

【中文标题】如何使用 Reflection.Emit 注入文字表达式?【英文标题】:How can in inject a literal expression using Reflection.Emit? 【发布时间】:2010-08-19 14:38:35 【问题描述】:

我正在开展一个项目,以使用 C# 作为脚本语言来评估具有不同复杂性的标记化用户定义表达式。

我有一个使用 CodeDOM 和反射的工作模型来生成评估器类,创建和加载程序集 (GenerateInMemory = true),实例化类,并执行评估方法。但是,我想在 AppDomain 中加载程序集,以便在执行完成时可以将其卸载。在研究这个问题时,我被引导到 AppDomain.DefineDynamicAssembly 方法。这似乎正是我所需要的,因为我可以创建一个可收藏的程序集。

以下是几个用户定义表达式的示例,以及我的 CodeDOM 项目生成的类:

简单的用户定义表达式:

return Abs(@HDL@/@LDL@ * 5.5);

生成的类:

namespace Lab.ResultProcessing


    public sealed class ExpressionEvaluator
    
        public double Evaluate()
        
            return System.Math.Abs(449.86881550861/74.934407754305 * 5.5);
        
    

更复杂的用户定义表达式:

double GFR;
double MA_GFR;
double MB_GFR;
double FA_GFR;
double FB_GFR;

GFR = (170 *
        Pow(@CREAT@, -0.999) *
        Pow(@YEARS@, -0.176) *
        Pow(@BUN@, -0.170) *
        Pow(@ALBUMIN@, 0.318));

MA_GFR = GFR;
MB_GFR = GFR * 1.180;
FA_GFR = GFR * 0.762;
FB_GFR = GFR * 1.180 * 0.762;

if (("@RACE@" != "B") && ("@GENDER@" == "M"))

    return MA_GFR;

else if (("@RACE@" == "B") && ("@GENDER@" == "M"))

    return MB_GFR;

else if (("@RACE@" != "B") && ("@GENDER@" == "F"))

    return FA_GFR;

else if (("@RACE@" == "B") && ("@GENDER@" == "F"))

    return FB_GFR;

else

    return GFR;

生成的类:

namespace Lab.ResultProcessing


    public sealed class ExpressionEvaluator
    
        public double Evaluate()
        
            double GFR;
double MA_GFR;
double MB_GFR;
double FA_GFR;
double FB_GFR;

GFR = (170 *
        System.Math.Pow(0.797258181752292, -0.999) *     
        System.Math.Pow(63.6814545438073, -0.176) *
        System.Math.Pow(5.47258181752292, -0.170) *       
        System.Math.Pow(3.79725818175229, 0.318));    

MA_GFR = GFR;                                   
MB_GFR = GFR * 1.180;                           
FA_GFR = GFR * 0.762;                           
FB_GFR = GFR * 1.180 * 0.762;                   

if (("B" != "B") && ("M" == "M"))

    return MA_GFR;                              

else if (("B" == "B") && ("M" == "M"))

    return MB_GFR;                              

else if (("B" != "B") && ("M" == "F"))

    return FA_GFR;                              

else if (("B" == "B") && ("M" == "F"))

    return FB_GFR;                              

else

    return GFR;

;
        
    

我现在尝试使用 Reflection.Emit 复制上述功能。我的问题是我还没有找到一种方法将去标记化的公式注入到发出的类中。

这是我正在使用的代码:

public static object DynamicEvaluate2(string expression)

    AssemblyName assemblyName = new AssemblyName("Lab.ResultProcessing");
    AppDomain appDomain = AppDomain.CurrentDomain;
    AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
    TypeBuilder typeBuilder = moduleBuilder.DefineType("ExpressionEvaluator", TypeAttributes.Sealed);
    MethodBuilder methodBuilder = typeBuilder.DefineMethod("Evaluate", MethodAttributes.Public | MethodAttributes.Final, typeof(double), null);
    ILGenerator methodGenerator = methodBuilder.GetILGenerator();

    methodGenerator.Emit(OpCodes.Ldobj, expression);
    methodGenerator.Emit(OpCodes.Ret);

    Type evaluatorType = typeBuilder.CreateType();
    MethodInfo methodInfo = evaluatorType.GetMethod("Evaluate");

    object evaluator = Activator.CreateInstance(evaluatorType);
    object result = methodInfo.Invoke(evaluator, null);

    return result;

调用 methodInfo.Invoke 方法时出现以下错误:

测试方法 ResultCalculatorTest.ResultCalculatorClassFactoryTest.DynamicEvaluate2Test 抛出异常: System.Reflection.TargetInvocationException:调用的目标已引发异常。 ---> System.BadImageFormatException: 错误的类令牌。

所以我有几个问题:

如何使用 Reflection.Emit 注入去标记化的用户定义表达式? 有什么方法可以查看发出类的 C# 代码,还是仅在 IL 中? 如何调试发出的类?

任何帮助将不胜感激。

【问题讨论】:

expression 字符串是什么? 表达字符串与上面列出的示例类似,例如“return Abs(@HDL@/@LDL@ * 5.5);”但它可能要复杂得多。 可以将 CodeDom 与您自己的 AppDomain 一起使用 - CodeDom 会生成一个实际的 .dll,您当然可以继续将其加载到您的 AppDomain 中。 (另外,在您的示例中,您没有使用单独的 AppDomain) Kirk,我想一旦项目能够正确执行,我就可以创建一个新的 AppDomain 并使用它来代替 CurrentDomain。 【参考方案1】:
methodGenerator.Emit(OpCodes.Ldobj, expression);

这并不符合您的要求:ldobj 指令需要 Type,而不是 string。根据MSDN,ldobj指令的用途是copy the value type object pointed to by an address。

与 CodeDom 不同,Reflection.Emit 不会为您解析表达式。您的代码将需要解析 expression 字符串并发出正确的 IL 操作码序列来计算该表达式。实际上,您需要编写自己的编译器。

Reflection.Emit 的替代方法是 System.Linq.Expressions 中的类型。这些比Reflection.Emit 高,比CodeDom 低。您仍然需要解析您的字符串,但您将在内存中构建一个抽象语法树,而不是发出原始操作码。

【讨论】:

我害怕那个。我不想解析用户定义的表达式。我正在恢复我原来的项目。

以上是关于如何使用 Reflection.Emit 注入文字表达式?的主要内容,如果未能解决你的问题,请参考以下文章

Reflection.Emit 中的短格式操作码错误

System.Reflection.Emit 动态实现接口

Protobuf-net & IL2CPP - System.Reflection.Emit 不受支持

[1]System.Reflection.Emit

反射-Emit

C# 脚本语言