Three options to dynamically execute C# code

I’ve been working in the past few months on a project where users can write C# to query data from a model. This means that I needed to dynamically execute C# code entered by the user.

There are basically three options for compiling and executing C# code dynamically:

  • Using the CodeDOM compiler
  • Using the Mono Compiler Service
  • Using Roslyn

Using the CodeDOM (Code Document Object Model), you can dynamically compile source code into a binary assembly. The steps involved are:

  1. Create an instance of the CSharpCodeProvider
  2. Compile an assembly from source code
  3. Get the type from the assembly
  4. Instantiate this type
  5. Get a reference to the method
  6. Call the method with the appropriate parameters

Here’s an example how to implement steps 1 and 2:

        private static Assembly CompileSourceCodeDom(string sourceCode)
        {
            CodeDomProvider cpd = new CSharpCodeProvider();
            var cp = new CompilerParameters();
            cp.ReferencedAssemblies.Add("System.dll");
            cp.GenerateExecutable = false;
            CompilerResults cr = cpd.CompileAssemblyFromSource(cp, sourceCode);

            return cr.CompiledAssembly;
        }

The other steps can then be implemented like this:

        private static void ExecuteFromAssembly(Assembly assembly)
        {
            Type fooType = assembly.GetType("Foo");
            MethodInfo printMethod = fooType.GetMethod("Print");
            object foo = assembly.CreateInstance("Foo");
            printMethod.Invoke(foo, BindingFlags.InvokeMethod, null, null, CultureInfo.CurrentCulture);
        }

Since you can only compile full assemblies with the CodeDOM, this means that:

  • You cannot execute snippets of code, you need a full class definition
  • Every time you compile some code a new assembly will be created and loaded

Using the Mono Compiler Service, you can also execute code dynamically. The big difference here (except the fact that you need a few other assemblies) is that MCS allows you to evaluate code with needing to define a class with methods so instead of this:

class Foo
{
    public void Print()
    {
        System.Console.WriteLine(""Hello benohead.com !"");
    }
}

You could just evaluate the following:

System.Console.WriteLine(""Hello benohead.com !"");

This is of course very useful when dynamically executing pieces of code provided by a user.

Here is a minimal implementation using MCS:

private static void ExecuteMono(string fooSource)
{
    new Evaluator(new CompilerContext(new CompilerSettings(), new ConsoleReportPrinter())).Run(fooSource);
}

If you need a return value, you can use the Evaluate method instead of the Run method.

One other advantage of MCS is that it doesn’t write an assembly to disk and then loads it. So when you evaluate 1000 expressions, you don’t end up loading 1000 new assemblies.

The last and latest option to handle dynamic C# code execution is to use the .NET Compiler Platform Roslyn. Roslyn is a set of open-source compilers and code analysis APIs for C# and Visual Basic.NET.

Dynamically executing code with Roslyn is actually pretty similar to the way you would do it with the CodeDOM. You have to create an assembly, get an instance of the class and execute the method using reflection.

Here is an example how to create the assembly:

        private static Assembly CompileSourceRoslyn(string fooSource)
        {
            using (var ms = new MemoryStream())
            {
                string assemblyFileName = "gen" + Guid.NewGuid().ToString().Replace("-", "") + ".dll";

                CSharpCompilation compilation = CSharpCompilation.Create(assemblyFileName,
                    new[] {CSharpSyntaxTree.ParseText(fooSource)},
                    new[]
                    {
                        new MetadataFileReference(typeof (object).Assembly.Location)
                    },
                    new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
                    );

                compilation.Emit(ms);
                Assembly assembly = Assembly.Load(ms.GetBuffer());
                return assembly;
            }
        }

Note that I do not handle any error scenarios here (or in the other pieces of code in this post) but to use it in a production system, you’d of course need to do it.

As with CodeDOM, a new assembly is created every time code is dynamically compiled. The difference is that you need to create a temporary assembly name yourself.

Executing the code from the assembly is then the same as with the CodeDOM.

Now let’s look at it from a performance perspective. Running the dynamic execution 100 times in a row for using the three methods, I got the following results:

roslyn: assemblies loaded=114
roslyn: time=2049,2049 milliseconds

codedom: assemblies loaded=100
codedom: 7512,7512 milliseconds

mono: assemblies loaded=6
mono: time=15163,5162 milliseconds

Running it multiple times has shown that the numbers are pretty stable.

So you can see that with Roslyn and CodeDOM, the number of assemblies loaded is increasing with the number of runs. The 14 more assemblies with Roslyn as well as the 6 assemblies with MCS are due to the fact that the assembly required by the compilers themselves are not loaded when the program is started but when they are used.

From a number of loaded assemblies point of view, MCS is a clear winner. It doesn’t need to load any assemblies except the assemblies required by the compiler.

From a performance perspective, Roslyn is quite impressive being more than 3 times faster than the CodeDOM and more than 7 times faster than MCS.

The main disadvantage of Roslyn versus CodeDOM is that Roslyn is not yet production ready and is pretty much work in progress. Once Roslyn is more feature complete and production ready, there will be no reasons to further use CodeDOM for dynamic code execution.

Although it’s the slowest solution, MCS is the one which scales better so if you have to execute a non-finite number of dynamic code, it’s the way to go. Also if you are just executing snippets of code and not whole classes, it makes your life easier.

 

5 thoughts on “Three options to dynamically execute C# code

  1. Thanks for the good information. I wonder if you could explain why you say this “Also if you are just executing snippets of code and not whole classes, it makes your life easier.”? What is exactly wrong with creating a class and executing some of its methods? Thanks much.

    1. The software I was writing when investing these possibilities allowed the user to write some pieces of C# code in an editor in order to get or write data. I had built a web-based C# editor for this. The advantage of being able to directly compile and execute snippets of code was that the error messages I got referenced the right line and column numbers. If I had to create a class and methods around the code provided by the user, I’d have to map the line and column numbers returned by the compiler so that it matches what the user sees (which is the code without class or method context).

      So basically the advantage is being able to use C# as a scripting language without needing to write structures like classes and methods which we don’t need and are otherwise only required so that the compiler doesn’t complain.

  2. Hi, do you share by any chance the web-based C#-Editor for MCS scripting? I basically “did” exactly the same, though atm its only in FireBug with WebSockets. :^)

  3. I already tested your code and I realize that if you put always the same name to the assembly name in roslyn, only one asembly is loaded. So, in that case, the number of assemblies loaded using Roslyn must be 14

  4. Thanks for the info. Was wondering whether you ran performance tests on the actual code execution.

    Instead of measuring how long it takes to load the assemblies, it would be interesting to see how fast the actual code is executed once it is loaded.

    Perhaps if it all boils down to IL there shouldn’t be any big difference.

Leave a Reply

Your email address will not be published. Required fields are marked *