Spencer Prahl

.Net Developer & Architect

  Home  ::  Contact  ::  Syndication  ::  Login
  9 Posts  ::  3 Stories  ::  8 Comments  ::  5 Trackbacks  
Use the .Net CodeDOM to compile C# On the Fly

Use the .Net CodeDOM to compile C# On the Fly

 Download the sample 

Wouldn't it be nice if you could extend an existing application's functionality without re-compiling the entire application? There is a solution! The .Net CodeDOM provides a way to do just that. The CodeDOM provides a way to create a source code object graph by defining source code elements. Once these source code elements are defined, the CodeDOM can be used to compile the objects.

Say you have an application that allows data to be updated in a database. Users supply the changes through a user interface and submit the changes. Now, different types of users have different levels of access to the data and different validation must be applied for these different user types. You build the application with all of the various user rules defined and your manager comes along and asks you to add a new rule for a new type of user. You tell him you have to schedule a full release of your application and it will take a month of development and testing before the change can be released.

Instead, you decide to implement some simple code that allows you to deploy un-compiled C# code for each rule. To do this, you use the CodeDOM. It can be really simple or very complex depending upon your needs. Begin by defining the CodeDomProvider.  Several languages are supported by CodeDomProvider implementations that ship with the .NET Framework SDK.

  • C#
  • Visual Basic
  • Managed C++
  • J#.NET
  • JScript

For this sample, I will use the C# provider as declared below:

1CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

Next, I create the compiler and supply some compiler parameters. Also, I add a reference to the System.dll so the code can do something.

1 ICodeCompiler compiler = provider.CreateCompiler();
2 CompilerParameters parms = new CompilerParameters();
3
4 // Configure parameters
5 parms.GenerateExecutable = false;
6 parms.GenerateInMemory = true;
7 parms.IncludeDebugInformation = false;
8
9 parms.ReferencedAssemblies.Add("System.dll");

The CompilerParameters object allows me to configure the type of assembly that I am going to compile. In my sample I want to generate a dll assembly so I set the GenerateExecutable parameter to false. A also want the compiled assembly to be in-memory instead of saved to a file so I set the GenerateInMemory parameter to false. The IncludeDebugInformation parameter allows me to control whether I want a debug version. Finally, I add the reference to the System.dll. There are several additional parameters available but these will suffice for this example.

Now we get to the meat of  the CodeDOM. This is where I actually create the code items to be compiled. This sample is very simplistic and there are many complex ways of adding code. Begin by creating a CodeCompileUnit. This is the base for a unit of code that will be compiled and contains all of the namespaces, types, fields, properties, and methods that will be compiled. We have a shell, so what comes next. The namespace of course. The CodeNamespace allows me to add the namespace definition to the CodeCompileUnit. Of course we need to add our “using“ statement for the System namespace. The System using is added to the RulesScript namespace by adding it to the Imports collection as a new CodeNamespaceImport. Now comes the class definition. The class is defined using the CodeTypeDeclaration object. Now add some code. Here I used the CodeSnippetTypeMember to add an entire function definition. The function doesn't do much but it could. There are many other methods for adding code. The CodeSnippetTypeMember is the easiest way however it does assume that the rest of the code conforms to the language that is used to define the function. In this example, all of the code is assumed to be C# so the function must be written in C# as well. If you want to create a more generic business rules compiler, you can take advantage of the lower level CodeDOM objects that allow you to add specific, detailed items to your code piecemeal.

Now that the code is defined, it is added to the ScriptFunctions type and finally the ScriptFunctions class is added to the namespace. This completes the CodeCompileUnit and it is ready to be compiled.

1 // Create a new CodeCompileUnit to contain the program graph
2 CodeCompileUnit compileUnit = new CodeCompileUnit();
3 // Declare a new namespace 
4 CodeNamespace rulesScript = new CodeNamespace("RulesScript");
5 // Add the new namespace to the compile unit.
6 compileUnit.Namespaces.Add(rulesScript);
7
8 // Add the new namespace using for the required namespaces.
9 rulesScript.Imports.Add( new CodeNamespaceImport("System") ); 
10 
11 // Declare a new type called ScriptFunctions.
12 CodeTypeDeclaration scriptFunctions = new CodeTypeDeclaration("ScriptFunctions");
13
14 // Add the code here
15 string code = @"public static string StringTest()
16 {
17 return ""the test"";
18 }";
19
20 CodeSnippetTypeMember mem = new CodeSnippetTypeMember(code);
21 scriptFunctions.Members.Add(mem);
22
23 // Add the type to the namespace
24 rulesScript.Types.Add(scriptFunctions);

To compile using the CodeCompileUnit, use the CompileAssemblyFromDom method of the code compiler. Other methods exist that allow you to compile a stream of code or code defined in a file. For our purposes, compiling from the DOM works best.

After the code is compiled, you can check the CompilerResults.Errors collection. If the compile failed, the errors collection will contain the detailed error messages.

1 CompilerResults results = compiler.CompileAssemblyFromDom(parms, compileUnit);
2
3 if (results.Errors.Count > 0)
4 {
5 // There are errors
6 CompilerErrorCollection errors = results.Errors;
7 }

Now we have an assembly compiled in memory but how do we use it? You'll notice that our function is defined as static. This allows me invoke the function without instantiating the object. This simplifies the execution of my code. I say “Invoke” because that is exactly what is done. The function “StringTest” is invoked using .NET Reflection.

The CompilerResults contains the CompiledAssembly property which is the actual resulting assembly containing our namespace, class, and method. The GetType() method returns the ScriptFunctions type from the assembly and the GetMethod() method returns the StringTest method. Since the StringTest method is static, the first parameter to the Invoke() method is null. The second parameter contains an object array that corresponds to each of the parameters to the method being invoked. Since our StringTest method doesn't have any parameters, I send an empty object array. The return object is the return value from the StringTest function. In this case, the variable “o“ should contain the text “the test“ if all goes well.

1 Type t = results.CompiledAssembly.GetType("RulesScript.ScriptFunctions");
2 MethodInfo info = t.GetMethod("StringTest");
3 object o = null;
4 if (parms == null) parms = new object[0];
5 if (info != null)
6 o = info.Invoke(null, parms);

The examples and code segments above outline a very simplistic method of using the CodeDOM. There are many advanced features of the CodeDOM and I encourage anyone to take a look. Some features of the CodeDOM provide code generation which could be used to generate code templates. Also, the CodeDOM is extensible and could be used as a type of compiler for additional languages. 

I have built a small sample application and some test cases that you can get  here 


Feedback

# Use the .Net CodeDOM to compile C# On the Fly

# Mock Unit Testing with Dependency Injection

# re: Use the .Net CodeDOM to compile C# On the Fly
blogs.crsw.com


Post Feedback
Title: 
Name: 
Url: 

Comments: 
Enter the code you see: