Easily execute dynamic C# using extension methods

Code has now been released under the SharpByte project to execute dynamic C# scripts (and evaluate statements) more easily than ever before. Dynamic code execution during earlier days of .NET was a sore spot, with many lamenting the lack of a functional equivalent to the JavaScript eval() function. For years many developers attempted hacks like using ASP.NET’s DataBinder.Eval(), but results were often subpar and performance was lackluster. Compiling to the CodeDom and the newer .NET Compiler Platform, a.k.a. Roslyn, can be moderately simple to complex depending on need, but many developers just want a simple, easy-to-reuse solution for supporting dynamic code entry in an application.

Further documentation on the easy-to-use API will be forthcoming, but for now these steps will suffice for anyone wishing to play with the code:

1. Either build and reference the project’s core assembly in your project, or import the code directly into your project.

2. If the code was built with conditional compilation symbol GLOBAL_EXTENSIONS, all objects will be able to use the dynamic-execution extension methods. Otherwise, if COLOCATE_EXTENSIONS was used, add a using statement for the System.Runtime.CompilerServices namespace; if neither GLOBAL_EXTENSIONS nor COLOCATE_EXTENSIONS was used, add a using statement for the SharpByte.Dynamic namespace.

3. Call any version of .Execute() or .Evaluate() directly on any object. The former will execute any C#-compliant script composed of properly semicolon-terminated lines of code, with “this” references executed on the object on which the extension methods are called (i.e. the context object for the call); the latter will evaluate a C# expression and return the result.

Once these steps are done, calling a script is as easy as this:

someObject.Execute("[script statements go here, and may be many lines]");

To anyone curious enough to understand the working of these extension methods, the code will be illustrative. Essentially, the extension methods call into a facade for compilation features of the .NET framework, and can be used to front-end calls to the CodeDom, the .NET Compiler Platform (“Roslyn”), or any other compiler, vastly simplifying the most-needed dynamic code compilation and execution features of each.

Here’s an example of the relatively complex task of building code using the CodeDom (without any attempt to slam the useful-looking code at this page, just provide an example of the complexity hidden away). Roslyn provides many enhancements over CodeDom, but still to simply execute a script, such as user-entered code in a CMS or other data system, isn’t always completely simplified as it could be.

The provided reference code compiles code constructed on-the-fly using the referenced compiler. A code formatter emits source code, without needing any special knowledge of the underlying compiler. A quick review of the System.Object extension methods involved shows how easy it is to retain a reference to the compiled IExecutable instance as well, which can be used to inspect the built-in execution log and timings, as well as any exception generated by the last run of a compiled executable. A unique signature based on the executable code type (expression/statement or script), parameter names, and source code is used on subsequent calls to check for pre-existing compiled executables, stored in the ExecutableFactory hybrid factory/collection for reuse.

Each executable can be compiled successfully with numbered placeholder values, a la string.Format() (but using triple curly braces to avoid .NET and Handlebars-style format interference) and/or named parameter values. As mentioned above, the context object (when using extension methods, the object on which the method is called) is used for any references to “this” within any script or expression.

Since an object from each compiled executable type has a Copy() method, it can safely and cheaply be used to create further executables of the same type. Calling Execute() on any particular executable is guaranteed to be thread-safe due to use of synchronization; for that reason, it’s easiest to cache local copies of reusable expressions/scripts.

Performance-wise, on a fairly low-spec laptop, formatting source code for a new class tests in the 1-2 microsecond range; post-compilation, execution of a script or expression can take well under a microsecond (e.g. a two-parameter complex mathematical expression which tests in the 800-nanosecond range), depending of course on complexity. The bulk of this fairly small overhead is due to the use of dynamic variables within the compiled classes themselves. If warranted, type safety may be added to the context object and/or named parameters to boost peformance further.

This generic utility code was originally developed in support of the SharpByte CMS, but is provided separately under the MIT License. Happy coding!

Advertisements