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

Wel*_*.59 5 .net assemblies reflection.emit appdomain

我正在研究一个项目来评估使用C#作为脚本语言的不同复杂度的标记化用户定义表达式.

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

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

简单的用户定义表达式:

return Abs(@HDL@/@LDL@ * 5.5);
Run Code Online (Sandbox Code Playgroud)

生成的类:

namespace Lab.ResultProcessing
{

    public sealed class ExpressionEvaluator
    {
        public double Evaluate()
        {
            return System.Math.Abs(449.86881550861/74.934407754305 * 5.5);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

生成的类:

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;
}
;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我现在正尝试使用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;
}
Run Code Online (Sandbox Code Playgroud)

调用methodInfo.Invoke方法时,我收到以下错误:

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

所以我有几个问题:

如何使用Reflection.Emit注入用户定义的表达式?
有没有办法查看发出的类的C#代码,还是仅在IL中?
如何调试发出的类?

任何帮助将不胜感激.

Tim*_*son 5

methodGenerator.Emit(OpCodes.Ldobj, expression);
Run Code Online (Sandbox Code Playgroud)

这不符合你的要求:ldobj指令要求a Type,而不是a string.根据MSDN,该ldobj指令的目的是复制地址指向的值类型对象.

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

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