Compiling c# code at Runtime and creating a simple maths formula evaluator
By pete rainbow
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
Popularity (2215 Views)
 |
| 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 |
|
Article Discussion: Compiling c# code at Runtime and creating a simple maths formula evaluator