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