BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

.NET Framework
.NET 3.5+

C# Runtime Compilation

The .NET framework includes classes that allow the code generator and compiler to be controlled from within an assembly. This allows C# source code, held in a string array, to be compiled at run time and executed using basic reflection techniques.

Executing the Compiled Method

Once the assembly has been compiled and is held in memory, we can execute the method using reflection. To make access to the reflection classes simpler, add the following using directive:

using System.Reflection;

We won't look into reflection in detail as it is beyond the scope of this article. Simply add the following code to the end of the Main method, after the call to CompileAssemblyFromSource. The first line obtains a reference to the DynamicCode class. The second line gets the method and the third line executes it.

var cls = results.CompiledAssembly.GetType("DynamicNS.DynamicCode");
var method = cls.GetMethod("DynamicMethod", BindingFlags.Static | BindingFlags.Public);
method.Invoke(null, null);

You can now run the program. The output should be as follows:

Hello, world!

Dynamic Compilation of a Method With a Return Value

For the second example we will perform a slightly more complex task. This time we will ask the user to input an expression that could be evaluated and its result assigned to a variable using C#. We will then compile this expression within a method that returns the result. Finally, we'll use the returned result within the main code, outputting it to the console.

We'll start by creating a CSharpCodeProvider and setting up the parameters. Add the code below to the Main method of a new project:

CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.ReferencedAssemblies.Add("System.dll");

The program will loop indefinitely, asking the user for an expression and outputting it until the user provides an empty string. This is handled by the while loop shown below. The compilation of the dynamic code will be handled by the as yet unwritten ShowExpressionResults method.

string expression = null;
while (expression != "")
{
    Console.WriteLine("Enter an expression");
    expression = Console.ReadLine();
    if (expression != "")
    {
        ShowExpressionResults(provider, parameters, expression);
    }
}

The ShowExpressionResults method obtains the code to be compiled from the GetCode method, passing the expression string entered by the user. The code is then compiled and reflection is used to execute the generated method and obtain the result. Note that the Invoke method is called using the same parameters but this time the return value is obtained and outputted to the console. As we don't know the result type for the expression that the user enters, we are converting it to a string before displaying it. When you do know the type, you can cast the return value of Invoke appropriately.

static void ShowExpressionResults(
    CSharpCodeProvider provider, CompilerParameters parameters, string expression)
{
    CompilerResults results = provider.CompileAssemblyFromSource(
        parameters, GetCode(expression));
    var cls = results.CompiledAssembly.GetType("DynamicNS.DynamicCode");
    var method = cls.GetMethod("DynamicMethod", BindingFlags.Static | BindingFlags.Public);
    Console.WriteLine(method.Invoke(null, null).ToString());
    Console.WriteLine();
}

Finally, we need to add the method that creates the code to compile at runtime. This time we are creating a method that returns the result of the expression entered by the user. We are using an object as the return value of the method in the string array to allow any valid user-entered expression to be evaluated.

static string[] GetCode(string expression)
{
    return new string[]
    {
        @"using System;

        namespace DynamicNS
        {
            public static class DynamicCode
            {
                public static object DynamicMethod()
                {
                    return " + expression + @";
                }
            }
        }"
    };
}

Testing the Dynamic Expression

You can now run the code and enter some expressions to see the results. The box below shows some sample input and output from the console window:

Enter an expression
1 + 5 * 10
51

Enter an expression
"Hello, " + "world!"
Hello, world!

Enter an expression
Math.Max(100,200)
200

Compilation Errors

A key problem with the above code is that if the user enters an invalid expression an exception is thrown and the program crashes. This exception is not caused by the compilation process; it occurs when using reflection to interrogate the generated assembly, which will be null when a build fails. To provide resilience we should check for errors after the build and not attempt to use the assembly if the build fails.

The CompilerResults object includes a property named Errors that can be used to determine if the build fails. Firstly, the Errors property has a Boolean property, named HasErrors, that will be false if the compilation was successful and true otherwise. If the value is true, you can enumerate the Errors property to obtain a list of all of the problems. Each is returned in a CompilerError object, which contains various properties including ErrorText.

To make use of these properties, modify the ShowExpressionResults method as follows. Note the if statement that checks the HasErrors property and the loop within the else portion that outputs the errors to the console.

static void ShowExpressionResults(
    CSharpCodeProvider provider, CompilerParameters parameters, string expression)
{
    CompilerResults results
        = provider.CompileAssemblyFromSource(parameters, GetCode(expression));

    if (!results.Errors.HasErrors)
    {
        var cls = results.CompiledAssembly.GetType("DynamicNS.DynamicCode");
        var method
            = cls.GetMethod("DynamicMethod", BindingFlags.Static | BindingFlags.Public);
        Console.WriteLine(method.Invoke(null, null).ToString());
    }
    else
    {
        foreach (CompilerError error in results.Errors)
        {
            Console.WriteLine(error.ErrorText);
        }
    }

    Console.WriteLine();
}

You can now run the program again to try a mixture of valid and invalid expressions.

25 September 2011