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.

Runtime Compilation

An interesting aspect of the .NET framework is that it provides a number of classes that allow you to interact with the compiler at run time. This allows you to create C# code in a string and compile it, either storing the resultant assembly in a file or holding it in memory where it can be accessed and executed using reflection. Using the compiler in this manner allows you to provide a means to include complex user customisations. For example, you could allow code to be entered by the user for execution as part of the workflow of your software. You might also use this facility to allow the user to enter C# expressions that are evaluated and used within a report. You may even create your own integrated development environment (IDE).

In this article we will create two examples that compile code to an in-memory assembly at runtime. We will then execute the code to see the results. The first example will be a basic console application where the dynamically compiled source code outputs a message. The second example will allow the user to input an expression to be evaluated and will use the result. We will consider the possibility of the user entering invalid code.

C# Code Compiler

The C# code generator and compiler are made accessible via the CSharpCodeProvider class, which is found in the Microsoft.CSharpnamespace:

using Microsoft.CSharp;

To enable dynamic compilation we need to create a CSharpCodeProvider instance. We will later use its methods to generate the object code. Add the following code to the Main method of the console application project.

CSharpCodeProvider provider = new CSharpCodeProvider();

Compiler Parameters

As when using Visual Studio, there are many compiler options that may be configured. Rather than being controlled with multiple properties of the CSharpCodeProvider class, most options are initialised in an instance of the CompilerParameters class, which is found in the System.CodeDom.Compiler namespace:

using System.CodeDom.Compiler;

To instantiate the object, add the following line of code to the Main method.

CompilerParameters parameters = new CompilerParameters();

For the purposes of this article we will use two properties of the CompilerParameters object. As we will not need to reuse the dynamically compiled assembly at a later time, it is most efficient to hold the results of the compilation in memory and not store it on disk. We can specify this by setting the GenerateInMemory property to true.

parameters.GenerateInMemory = true;

In Visual Studio when you create a project you might add references to external assemblies that contain code that you wish to utilise. When compiling dynamically at runtime, these references are added to the ReferencedAssemblies property of the CompilerParameters object. For example, the following line adds a reference to System.dll, making the types it contains available.

parameters.ReferencedAssemblies.Add("System.dll");

Compiling the Code

The next step is to add the code that compiles some other code dynamically. To do so we use the CompileAssemblyFromSource method, passing the compiler options and the code to be compiled to its parameters. The code that will be compiled is provided as a string array. You can think of each element in the array as a string that contains the contents of a source file that would otherwise be created in Visual Studio. Each of the individual string elements must contain code that compiles correctly in order for the operation to succeed.

The result of the CompileAssemblyFromSource method is a CompilerResults object. If the code builds successfully, this holds the generated assembly in the CompiledAssembly property. If the build fails, the results can be interrogated to determine the problem. We will see this later in the article.

To compile the code, add the following line to the Main method. Note that the code to build is obtained from a method that we have yet to write:

To complete the first example we need to add the method that returns the string array of source code. This is shown below. Note that we are creating a string array with a single element. This element contains a using directive, a namespace, a class and a method. The method outputs the text, "Hello, world!" to the console. The text assigned to the string is formatted with indentation to show its structure but this is not necessary.