Compiling c# code at Runtime and creating a simple maths formula evaluator

One of the exciting things of using a interpreted language like c# is the ability to dynamically execute code at runtime using reflection. Here we go one step further and actually create new code and compile and run at runtime. We'll use the simple case of evaluating a simple maths formula entered by the user.

Runtime C# Compilation

So the first thing you'll need to have a look at is the CSharpCodeProvider, ( note that there are providers for JavaScript and VB.Net if they are your persuasion.

This is a simpler and improved version of the compiler from the first days of .net, but follows the same form.

You follow the same steps as you do when editing your own projects.

    1. First you need to configure the compiler, just as you do when setting the properties on your projects in the IDE.
    2. Then setup the source code
    3. Then compile the code
    4. Check for errors and fix, return to step 2
    5. Create instance and run

Creating the compiler is a simple step

            CSharpCodeProvider codeProvider = new CSharpCodeProvider();

Now we need to configure the compiler, the equivalent of editing a projects properties.

This is done using the CompilerParameters

             CompilerParameters compilerparams = new CompilerParameters();

As you'd expect this class has all of the options you need that you set on a typical project, from the assembly references to the Including debug symbols.

Here we shall just use the basic set to enable us to generate runtime code.

            compilerparams.GenerateExecutable = false;  // we don't want an exe as we'll be using the dll assembly right here
            compilerparams.GenerateInMemory = true;    // we'll be doing this in memory as creating a file lowers performance

             // now we need to add the assembly references, this is important
            // first we'll add our own exe so we can use any of our own bespoke code
            // then we'll add some standard assemblies, you can add as many or as few as you like
            compilerparams.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetName().Name + ".exe");
            compilerparams.ReferencedAssemblies.Add("System.dll");
            compilerparams.ReferencedAssemblies.Add("System.Core.dll");

You could even go so far as iterating the assemblies loaded by the current application and adding them or even allow the user to specify the refences.

Now for the important step of compiling your code

            CompilerResults results = codeProvider.CompileAssemblyFromSource(compilerparams, codeStr);

We'll come back to what the results are, but first we need to have a look at what codeStr contains.

Well it's just the same as a typical .cs file and in fact you can pass an array of them to the compiler.

So lets have a look at a simple example

string codeStr = @"
                 public static class " + className + @"  
                 {
                     public static double Eval()
                     {
                            return 3*4 - 6*7";
                       }
                 }";

or perhaps to allow a user to specify their own formula

string codeStr = @"
                 public static class " + className + @"  
                 {
                     public static double Eval()
                     {
                            return " + formula + @";
                       }
                 }";

Note that i haven't put any using statements or namespaces at the top, this is just me being lazy and of course you can and should where needed.

Looking at the CompilerResults

First you'll need to see if it's compiled successfull

            if (results.Errors.HasErrors)
            {
                foreach (CompilerError error in results.Errors)
                 {
                      Debug.WriteLine(error);
                 }
             }

as you'd expect the CompilerError has all of the usual information, line number, text etc

You can also look at any warnings too.

Running the code

Right now for the fun bit, running the code.

This is just the usual reflection mechanism, the assembly we have created is now loaded and available

In the example above we just need to get hold of the static method, in a wider example we may want to use the Activator class to create an instance and then execute a reflected method.

       Object myEvalMethod = results.CompiledAssembly.GetType(className).GetMethod("Eval", System.Reflection.BindingFlags.Static |      BindingFlags.Public);
      Object value = myEvalMethod.Invoke(null, null);

Note that with reflection we just get an object back so you may want to cast it to a known type.

Conclusions

You can have a look at a complete class here

A couple of things to watch for is that it is quite a heavy weight operation to do the compile step and so this should be cached if possible.

You also may need to change the class name to make unique if repeated calls to the compiler are made.

In my next article I'll be looking at how to use this technique to adding formula columns to the DataGridView

By pete rainbow   Popularity  (4335 Views)
Picture
Biography - pete rainbow
Windows Gui Developer with many years of experience from MFC days right through to WPF

Currently looking for any work in the London area