field1,field2,field3,((field4,field5)|(field6)), which means: fields1-3 MUST exist, AND EITHER (Field4 AND 5) OR Field6 must exist
In other words, what I needed to do was create a logical-expression parser. Now one way to do this would be to manually parse the string, i.e. look for the innermost brackets, convert the expression to booleans, evaluate that, then evaluate the next 'level' etc. Now not only would this get pretty complicated, but it's a little limiting.
Ideally I would have loved to create a lambda expression dynamically (i.e. convert a string to a lambda), however this functionality was not readily available. NOTE: in .NET4, it looks like this is covered with the DynamicExpression class, but I'm unfortunately limited to 3.5 atm.
What I went for in the end is probably overkill, but the concept is pretty cool. C# obviously already handles mathematical expressions parsing . So I thought: "hey, why not format the evaluation into c# code and execute it?!". Well, this is actually pretty easy. It involves leveraging the CodeDom functionality. Now you can manually construct a class, as explained here, but that involves a fair bit of work. The option I went for was to use the CSharpCodeProvider class, which enables you to specify the class as text, and create an in-memory assembly from it.
It's as easy as this:
CompilerResults cr; Dictionary<string, string> d = new Dictionary<string, string>(); d.Add("CompilerVersion", "v3.5"); CSharpCodeProvider myCodeProvider = new CSharpCodeProvider(d); CompilerParameters cp = new CompilerParameters(new string[] { "DevSaffa.AReferencedProject.dll" }); cp.GenerateExecutable = false; cp.GenerateInMemory = true; cp.OutputAssembly = "OutMod"; string sourceText = "using System;" + "using DevSaffa.AReferencedProject;" + "namespace DevSaffa.DynamicCode{" + "public class Class1{" + "public static bool Evaluate(MyClass1 data){return " + logicalOperation + ";}}} "; cr = myCodeProvider.CompileAssemblyFromSource(cp, sourceText); if (cr.Errors.Count > 0) throw new ArgumentException("Expression cannot be evaluated, please use a valid C# expression");A couple of things:
- Be sure to create the CSharpCodeProvider instance specifying the CompilerVersion, otherwise you may run into issues around referenced assemblies.
- Also, see how you can reference one of your own projects: not only do you need to include the #using reference, but you also need to specify the referenced assembly in the compile-parameters
- If any errors occur with the compilation, you can iterate through the cr.Errors collection, which contain the actual compile errors (as would be displayed in your build-error window in Visual studio for a normal build)
field1,field2,field3,((field4,field5)|(field6))becomes
data.FieldExists("field1") && data.FieldExists("field2") && data.FieldExists("field3") && ((data.FieldExists("field4") && data.FieldExists("field5"))|(data.FieldExists("field6"))), where my data-class has a method:
public bool FieldExists(string code)
I can then call the compiled in-memory assembly's Evaluate() method like so:
MethodInfo Methinfo = cr.CompiledAssembly.GetType("DevSaffa.DynamicCode.Class1").GetMethod("Evaluate"); return (bool)Methinfo.Invoke(null, new object[] { data });(where 'data' is an instance of my record-type object (MyClass1) that I want to evaluate)
Nifty!
No comments:
Post a Comment