Как можно вставить буквальное выражение, используя Reflection.Emit? - PullRequest
5 голосов
/ 19 августа 2010

Я работаю над проектом по оценке токенизированных пользовательских выражений различной сложности, используя 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?
Как отладить выданный класс?

Любая помощь будет принята с благодарностью.

1 Ответ

5 голосов
/ 19 августа 2010
methodGenerator.Emit(OpCodes.Ldobj, expression);

Это не делает то, что вы хотите: инструкция ldobj ожидает Type, а не string.Согласно MSDN, цель инструкции ldobj состоит в том, чтобы скопировать объект типа значения, на который указывает адрес .

В отличие от CodeDom, Reflection.Emit не будет анализировать ваше выражение длявы.Ваш код должен будет проанализировать строку expression и выдать правильную последовательность кодов IL, чтобы вычислить это выражение.По сути, вам нужно написать собственный компилятор.

Альтернативой Reflection.Emit являются типы в System.Linq.Expressions.Это более высокий уровень, чем Reflection.Emit, и более низкий уровень, чем CodeDom.Вам все еще нужно будет проанализировать вашу строку, но вы будете строить абстрактное синтаксическое дерево в памяти вместо генерации необработанных кодов операций.

...