I have an application that processes table structures. Tables have columns and some columns may contain expressions. For example, an expression may look like “row.Price * row.Quantity”. The compiled code should be loaded into the current AppDomain. Also, I didn’t want to use the existing Roslyn Scripting API because I could not get my head around the handling of AppDomains and referencing assemblies in that (I had too little time to dig into that). If you have read this post, you might have an idea what’s comming next. The current post can be understood as an update to the older post with adaptions adjustments to the current version of Roslyn (0.6.4033103-beta).
So the basic idea is to embed the expression to be compiled into a C# class and to compile that class. The compiled class will be called each time the expression must be evaluated. To increase performance, some pre-compilation of Linq Expressions will be applied. Here we go…
Embed the expression into C# code
As stated before, an expression has the form “row.Price * row.Quantity”. We embedded that code into the following C# code:
using System; public class CompiledExpression { public static object Run(dynamic row2) { Func<dynamic, object> func = row => row.Price * row.Quantity; return func(row2); } }
I guess that every C# developer already sees what we can do with this construct. It allows us to support even more complex expressions when they are surrounded with curly brackets. For example, we could rewrite the expression as following:
{ var price = row.Price; var qty = row.Quantity; var result = price * qty; return result; }
Compile the generated C# code
Now that we have generated C# code containing the expression, we can compile it. We will compile it using Roslyn.
public static Assembly Compile(params string[] sources) { var assemblyFileName = "gen" + Guid.NewGuid().ToString().Replace("-", "") + ".dll"; var compilation = CSharpCompilation.Create(assemblyFileName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: from source in sources select CSharpSyntaxTree.ParseText(source), references: new[] { new MetadataFileReference(typeof(object).Assembly.Location), new MetadataFileReference(typeof(RuntimeBinderException).Assembly.Location), new MetadataFileReference(typeof(System.Runtime.CompilerServices.DynamicAttribute).Assembly.Location) }); EmitResult emitResult; using (var ms = new MemoryStream()) { emitResult = compilation.Emit(ms); if (emitResult.Success) { var assembly = Assembly.Load(ms.GetBuffer()); return assembly; } } var message = string.Join("\r\n", emitResult.Diagnostics); throw new ApplicationException(message); }
The code above is fairly easy to understand, I think, so I will skip further explanations. The good news is that when this method is successfully executed, it returns an assembly with one single class named “CompiledExpression” taht has on single static method named “Run”. So we can easily use this assembly right now:
object row = ... // this object contains the runtime data string code = ... // this is the generated C# code var assembly = Compile(code); var type = assembly.GetType("CompiledExpression"); var method = type.GetMethod("Run"); var result = method.Invoke(null, new object[] { row }); Console.WriteLine("Hurray, the expression returned " + result);
Apply pre-compiled Linq Expressions
The code above relies heavily on Reflection. Depending on the performance requirements for your application, you might want to speed things up a little using pre-compilation:
var assembly = Compile(code); var type = assembly.GetType("CompiledExpression"); var method = type.GetMethod("Run"); var func = (Func<DataRow, object>)method.CreateDelegate(typeof(Func<DataRow, object>)); var row = ... // get the runtime data var result = func(row);
Of course, the whole pre-compilation thing only makes sense if the resulting delegate gets cached, e.g. in a static dictionary.
In this post I have described a quick and dirty method to implement C# expressions. Well, it isn’t that dirty, actually. For real-world scenarios, the generated C# code could be more sophisticated, but this method is reliable and fast. You could alter the compiler error reporting and re-calculate line and column numbers so that they refer to the actual expression and not to the generated C# code. If you need more separation between the expression and your AppDomain, you can of course load the genearted assembly in a new AppDomain and call it there. Please see this post for details and remember that we have not saved the generated assembly to disk. So instead of loading the assembly from file as shown in that post, you must first load the assembly bytes using the AppDomain.Load method, then load the desired type by specifying the assembly name instead of the assembly location by using CreateInstanceAndUnwrap instead of AppDomain.CreateInstanceFromAndUnwrap.
Thanks for perfect tutorial. I’m trying to load created assembly into new AppDomain. That not works:
newAppDomain.Load(byte[]) throws FileNotFoundException. Has you tried that way?
Based on your comment, I added a new post. I hope it helps. Please look here: https://mhusseini.wordpress.com/2015/05/27/c-loading-dynamic-assemblies-into-other-appdomains/.