Introduction
This is a simple class library (or just .cs file if you wish) to allow for
runtime compilation and evaluation of C# code blocks. There are both
static methods of the Evaluator
class that allow for simple
use (but at a performance penalty) or you can use the object directly to create
multiple evaluations:
Console.WriteLine("Test0: {0}", Evaluator.EvaluateToInteger("(30 + 4) * 2"));
Console.WriteLine("Test1: {0}", Evaluator.EvaluateToString("\"Hello \" + \"There\""));
Console.WriteLine("Test2: {0}", Evaluator.EvaluateToBool("30 == 40"));
Console.WriteLine("Test3: {0}", Evaluator.EvaluateToObject("new DataSet()"));
EvaluatorItem[] items = {
new EvaluatorItem(typeof(int), "(30 + 4) * 2", "GetNumber"),
new EvaluatorItem(typeof(string), "\"Hello \" + \"There\"",
"GetString"),
new EvaluatorItem(typeof(bool), "30 == 40", "GetBool"),
new EvaluatorItem(typeof(object), "new DataSet()", "GetDataSet")
};
Evaluator eval = new Evaluator(items);
Console.WriteLine("TestStatic0: {0}", eval.EvaluateInt("GetNumber"));
Console.WriteLine("TestStatic1: {0}", eval.EvaluateString("GetString"));
Console.WriteLine("TestStatic2: {0}", eval.EvaluateBool("GetBool"));
Console.WriteLine("TestStatic3: {0}", eval.Evaluate("GetDataSet"));
How does it work? I am using the CodeDOM to create a simple assembly
with a single class in it. I simply transform each of the EvaluatorItem
s
into a method of the class. When you call EvaluateInt()
or Evaluate()
, I use reflection to get a MethodInfo
object
and call the method.
The source code comes packaged in a Class Library with a Test program that you
can use to get examples of use:
To compile the expressions, I am creating a new CSharpCodeProvider
and setting
Compiler attributes (like adding references, telling it to generate it in
memory, etc.). Then I am building a dummy class that I can append my
methods on. Lastly I compile it (check for errors) and save the object to
use to call with the MethodInfo
structure:
ICodeCompiler comp = (new CSharpCodeProvider().CreateCompiler());
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("system.dll");
cp.ReferencedAssemblies.Add("system.data.dll");
cp.ReferencedAssemblies.Add("system.xml.dll");
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
StringBuilder code = new StringBuilder();
code.Append("using System; \n");
code.Append("using System.Data; \n");
code.Append("using System.Data.SqlClient; \n");
code.Append("using System.Data.OleDb; \n");
code.Append("using System.Xml; \n");
code.Append("namespace ADOGuy { \n");
code.Append(" public class _Evaluator { \n");
foreach(EvaluatorItem item in items)
{
code.AppendFormat(" public {0} {1}() ",
item.ReturnType.Name,
item.Name);
code.Append("{ ");
code.AppendFormat(" return ({0}); ", item.Expression);
code.Append("}\n");
}
code.Append("} }");
CompilerResults cr = comp.CompileAssemblyFromSource(cp, code.ToString());
if (cr.Errors.HasErrors)
{
StringBuilder error = new StringBuilder();
error.Append("Error Compiling Expression: ");
foreach (CompilerError err in cr.Errors)
{
error.AppendFormat("{0}\n", err.ErrorText);
}
throw new Exception("Error Compiling Expression: " + error.ToString());
}
Assembly a = cr.CompiledAssembly;
_Compiled = a.CreateInstance("ADOGuy._Evaluator");
When I call the _Compiled
object, I use a MethodInfo
object to
Invoke the call and return the result to the user:
public object Evaluate(string name)
{
MethodInfo mi = _Compiled.GetType().GetMethod(name);
return mi.Invoke(_Compiled, null);
}