2010-08-19 18 views
5

Je travaille sur un projet d'évaluation d'expressions définies par l'utilisateur et de complexité variable, utilisant C# comme langage de script.Comment injecter une expression littérale à l'aide de Reflection.Emit?

J'ai un modèle qui utilise CodeDOM et la réflexion pour générer une classe d'évaluateur, créer et charger l'assembly (GenerateInMemory = true), instancier la classe et exécuter la méthode evaluate. Toutefois, je veux charger l'assembly dans un AppDomain afin que je puisse le décharger lorsque l'exécution est terminée. En recherchant ce problème, j'ai été dirigé vers la méthode AppDomain.DefineDynamicAssembly. Cela semble être exactement ce dont j'ai besoin, car je peux créer un ensemble de collection.

Voici quelques exemples des expressions définies par l'utilisateur, et les classes générées par mon projet CodeDOM:

simple expression définie par l'utilisateur:

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

classe générée:

namespace Lab.ResultProcessing 
{ 

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

Expression définie par l'utilisateur plus complexe:

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") && ("@[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; 
} 

classe générée:

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

Je tente maintenant de dupliquer les fonctionnalités décrites ci-dessus en utilisant Reflection.Emit. Mon problème est que je n'ai pas trouvé un moyen d'injecter la formule detokenized dans la classe émise.

Voici le code que je utilise:

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

Lorsque la méthode est appelée MethodInfo.Invoke je reçois l'erreur suivante:

Méthode d'essai ResultCalculatorTest.ResultCalculatorClassFactoryTest.DynamicEvaluate2Test a lancé exception: système. Reflection.TargetInvocationException: Une exception a été émise par la cible d'une invocation. ---> System.BadImageFormatException: jeton de classe incorrecte.

J'ai donc quelques questions:

Comment injecter dans l'expression définie par l'utilisateur à l'aide detokenized Reflection.Emit?
Y at-il un moyen de voir le code C# pour la classe émise, ou est-ce seulement dans IL?
Comment déboguer la classe émise?

Toute aide serait grandement apprécié.

+0

Qu'est-ce que la chaîne 'expression'? –

+0

La chaîne d'expression serait semblable aux exemples énumérés ci-dessus, tels que "return Abs (@HDL @/@ LDL @ * 5.5);" mais ça pourrait être beaucoup plus complexe. –

+1

Vous pouvez * utiliser * CodeDom avec votre propre AppDomain - CodeDom crachera un vrai fichier .dll que vous pourrez bien sûr charger dans votre AppDomain. (De plus, dans votre exemple, vous n'utilisez pas un AppDomain séparé) –

Répondre

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

Cela ne fait pas ce que vous voulez qu'il: l'instruction ldobj attend un Type, pas string. Selon MSDN, le but de l'instruction ldobj est copy the value type object pointed to by an address. À la différence de CodeDom, Reflection.Emit n'analysera pas votre expression pour vous. Votre code devra analyser la chaîne expression et émettre la bonne séquence d'opcodes IL pour calculer cette expression. En effet, vous devez écrire votre propre compilateur.

Une alternative à la réflexion.Emit est le type dans System.Linq.Expressions. Ce niveau est supérieur à Reflection.Emit et inférieur à CodeDom. Vous aurez toujours besoin d'analyser votre chaîne, mais vous allez construire une arborescence de syntaxe abstraite en mémoire au lieu d'émettre des opcodes bruts.

+0

J'avais peur de ça. Je ne veux pas entrer dans l'analyse des expressions définies par l'utilisateur. Je reviens à mon projet original. –