I am working on a project to evaluate tokenized user-defined expressions of varying complexity, using C# as the scripting language.
I have a working model using CodeDOM and reflection to generate an evaluator class, create and load the assembly (GenerateInMemory = true), instantiate the class, and Execute the evaluate method. However, I want to load the assembly in an AppDomain so that I can unload it when execution is complete. While researching this issue, I was directed to the AppDomain.DefineDynamicAssembly method. This seems to be exactly what I need, as I can create a collectible assembly.
Here are a couple of examples of the user-defined expressions, and the classes generated by my CodeDOM project:
Simple user-defined expression:
return Abs(@HDL@/@LDL@ * 5.5);
Generated class:
namespace Lab.ResultProcessing
{
public sealed class ExpressionEvaluator
{
public double Evaluate()
{
return System.Math.Abs(449.86881550861/74.934407754305 * 5.5);
}
}
}
More complex user-defined expression:
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;
}
Generated class:
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;
}
;
}
}
}
I am now attempting to duplicate the functionality described above using Reflection.Emit. My problem is that I haven't found a way to inject the detokenized formula into the emitted class.
Here is the code I am using:
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;
}
When the methodInfo.Invoke method is called I get the following error:
Test method ResultCalculatorTest.ResultCalculatorClassFactoryTest.DynamicEvaluate2Test threw exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.BadImageFormatException: Bad class token.
So I have a couple of questions:
How can in inject the detokenized user-defined expression using Reflection.Emit?
Is there any way to see C# code for the emitted class, or is it only in IL?
How do I debug the emitted class?
Any help would be greatly appreciated.