2010-08-19 4 views
5

Estoy trabajando en un proyecto para evaluar expresiones tokenizadas definidas por el usuario de complejidad variable, utilizando C# como lenguaje de scripting.¿Cómo se puede inyectar una expresión literal usando Reflection.Emit?

Tengo un modelo en funcionamiento usando CodeDOM y reflexión para generar una clase de evaluador, crear y cargar el ensamblado (GenerateInMemory = true), crear una instancia de la clase y Ejecutar el método de evaluación. Sin embargo, quiero cargar el ensamblado en un Dominio de aplicación para que pueda descargarlo cuando se complete la ejecución. Mientras investigaba este problema, me dirigieron al método AppDomain.DefineDynamicAssembly. Esto parece ser exactamente lo que necesito, ya que puedo crear un ensamblaje coleccionable.

Aquí hay un par de ejemplos de las expresiones definidas por el usuario, y las clases generadas por mi proyecto CodeDOM:

simple definida por el usuario expresión:

return Abs(@[email protected]/@[email protected] * 5.5); 

clase generada:

namespace Lab.ResultProcessing 
{ 

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

Expresión definida por el usuario más compleja:

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

GFR = (170 * 
     Pow(@[email protected], -0.999) * 
     Pow(@[email protected], -0.176) * 
     Pow(@[email protected], -0.170) * 
     Pow(@[email protected], 0.318)); 

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

if (("@[email protected]" != "B") && ("@GE[email protected]" == "M")) 
{ 
    return MA_GFR; 
} 
else if (("@[email protected]" == "B") && ("@[email protected]" == "M")) 
{ 
    return MB_GFR; 
} 
else if (("@[email protected]" != "B") && ("@[email protected]" == "F")) 
{ 
    return FA_GFR; 
} 
else if (("@[email protected]" == "B") && ("@[email protected]" == "F")) 
{ 
    return FB_GFR; 
} 
else 
{ 
    return GFR; 
} 

clase generada:

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; 
} 
; 
     } 
    } 
} 

Ahora estoy tratando de duplicar la funcionalidad descrita anteriormente, utilizando Reflection.Emit. Mi problema es que no he encontrado una forma de inyectar la fórmula detokenizada en la clase emitida.

Aquí está el código que estoy utilizando:

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; 
} 

Cuando el método se llama MethodInfo.Invoke me sale el siguiente error:

Método de ensayo ResultCalculatorTest.ResultCalculatorClassFactoryTest.DynamicEvaluate2Test lanzó excepción: sistema. Reflection.TargetInvocationException: la excepción ha sido lanzada por el objetivo de una invocación. ---> System.BadImageFormatException: token de clase malo.

así que tengo un par de preguntas:

¿Cómo se puede inyectar en la expresión definida por el usuario utilizando detokenized Reflection.Emit?
¿Hay alguna forma de ver el código C# para la clase emitida, o solo en IL?
¿Cómo depuro la clase emitida?

Cualquier ayuda sería muy apreciada.

+0

¿Qué es la cadena 'expression'? –

+0

La cadena de expresión sería similar a los ejemplos enumerados anteriormente, como "return Abs (@HDL @/@ LDL @ * 5.5);" pero podría ser mucho más complejo. –

+1

Usted * puede * usar CodeDom con su propio Dominio de Aplicación - CodeDom escupirá un .dll real que usted puede, por supuesto, proceder a cargar en su Dominio de Aplicación. (Además, en su ejemplo, no está usando un Dominio de aplicación diferente) –

Respuesta

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

Esto no hace lo que quiere: la instrucción ldobj espera una Type, no un string. De acuerdo con MSDN, el propósito de la instrucción ldobj es copy the value type object pointed to by an address.

A diferencia de CodeDom, Reflection.Emit no analizará su expresión por usted. Su código necesitará analizar la cadena expression y emitir la secuencia correcta de códigos de operación IL para calcular esa expresión. En efecto, necesitas escribir tu propio compilador.

Una alternativa a la reflexión.Emitir es los tipos en System.Linq.Expressions. Estos tienen un nivel más alto que Reflection.Emit y un nivel más bajo que CodeDom. Aún necesitará analizar su cadena, pero construirá un árbol de sintaxis abstracta en la memoria en lugar de emitir códigos de operación sin procesar.

+0

Tenía miedo de eso. No quiero entrar en el análisis de las expresiones definidas por el usuario. Estoy volviendo a mi proyecto original. –

Cuestiones relacionadas