Generate, Compile and Execute Code Dynamically

Like any good developer, when someone comes to my desk and says you cannot do something I have to prove them wrong. This was one of those challenges. He said you cannot “generate, compile and execute code on the fly in .NET”. I said yes you can, he said no you cannot and after five minutes of back and forth I decided to throw together this example.

Step 1: Build the code
Here you have 2 options: code as strings, code from CodeDom. What one you pick depends on what you are doing. In “Programming Microsoft ASP.NET 2.0 Applications Advanced Topics by Dino Esposito” he does a good job of explaining each. “Using a text-writer binds you to a particular language, but it allows you to write code more quickly. The CodeDom API is more abstract and certainly more complex and quirky to write. CodeDom requires a larger memory footprint then a text-writer based solution. In terms of readability, no approach is perfect. CodeDom is hard to read because of its level of abstraction; a text-writer solution has code interspersed with strings and placeholders, and it can be dangerously error-prone to modify and maintain.”

I decided to use the CodeDom technique. So for my example I am going to create a simple static add method that takes in two integers and adds them together and returns the results.

    1         //Step 1: Build the Code

    2         //Create the code unit that we are going to work with

    3         CodeCompileUnit code = new CodeCompileUnit();

    4 

    5         //Give it a namespace

    6         CodeNamespace ns = new CodeNamespace("GeneratedNamespace");

    7         code.Namespaces.Add(ns);

    8 

    9         //Create the class

   10         CodeTypeDeclaration cls = new CodeTypeDeclaration("GeneratedClass");

   11         cls.IsClass = true;

   12         cls.Attributes = MemberAttributes.Public;

   13         ns.Types.Add(cls);

   14 

   15         //Create the method

   16         CodeMemberMethod method = new CodeMemberMethod();

   17         CodeParameterDeclarationExpression val1 = new CodeParameterDeclarationExpression(typeof(int), "value1");

   18         CodeParameterDeclarationExpression val2 = new CodeParameterDeclarationExpression(typeof(int), "value2");

   19         method.Attributes = MemberAttributes.Public | MemberAttributes.Static;

   20         method.Name = "Add";

   21         method.Parameters.Add(val1);

   22         method.Parameters.Add(val2);

   23         method.ReturnType = new CodeTypeReference(typeof(int));

   24 

   25         method.Statements.Add(

   26             new CodeMethodReturnStatement(

   27             new CodeBinaryOperatorExpression(

   28             new CodeArgumentReferenceExpression("value1"),

   29             CodeBinaryOperatorType.Add,

   30             new CodeArgumentReferenceExpression("value2"))));

   31         cls.Members.Add(method);

Now that you have the code generated you might want to take a look at it. You can convert it to a string by doing the following:

    1         //Output the generated code to the page so we can see it

    2         StringWriter sw = new StringWriter(new StringBuilder());

    3         CodeDomProvider cdp = new CSharpCodeProvider();

    4         cdp.GenerateCodeFromCompileUnit(code, sw, null);

    5         Response.Write(sw.ToString().Replace("\n", "<br \\>"));

 
The generated code will look something like this. . .

    1 //------------------------------------------------------------------------------

    2 //

    3 // This code was generated by a tool.

    4 // Runtime Version:2.0.50727.1433

    5 //

    6 // Changes to this file may cause incorrect behavior and will be lost if

    7 // the code is regenerated.

    8 //

    9 //------------------------------------------------------------------------------

   10 

   11 namespace GeneratedNamespace

   12 {

   13     public class GeneratedClass

   14     {

   15         public static int Add(int value1, int value2)

   16         {

   17             return (value1 + value2);

   18         }

   19     }

   20 }

Step 2: Compile it
Now that you have your code (in a string or in the CodeDom) you can request that .NET compile it for you. First you need to determine what language you want to compile it using. I am going to use C# but you can easily change it out for VB, etc.

    1         //Step 2: Compile the Code

    2         CompilerParameters cp = new CompilerParameters();

    3         cp.GenerateExecutable = false;

    4         cp.GenerateInMemory = true;

    5         cp.ReferencedAssemblies.Add("System.dll");

    6         CompilerResults results = cdp.CompileAssemblyFromDom(cp, code);

    7         Assembly assembly = results.CompiledAssembly;

Step 3: Run it
Now it is just a matter of using reflection to take the assembly that we generated and invoke its method.

    1         //Step 3: Run it using reflection

    2         Type t = assembly.GetType("GeneratedNamespace.GeneratedClass");

    3         object[] parms = { 5, 5 };

    4         Response.Write(t.GetMethod("Add").Invoke(null, parms));

So while this was an academic exercise to prove that it can be done, it does have real implications for real applications, but I will leave that to your imagination.

Ideas borrowed from:
Dino Esposito - http://weblogs.asp.net/despos/
CodeDom Calculator - http://channel9.msdn.com/ShowPost.aspx?PostID=360076
GotDotNet - http://samples.gotdotnet.com/quickstart/util/srcview.aspx?path=/quickstart/howto/samples/CompMod/CodeDom/ListBuilder/listbuilder.src&file=CS\listbuilder.cs&font=3

Published Wednesday, December 12, 2007 10:17 AM by sweisfeld
Filed under: ,

Comments

No Comments