Discover Dynamic Code Compilation

Not sure where dynamic code compilation makes sense? A common scenario should help illustrate the need for it. Suppose you have to pull data out of one database and put it into another. Piece of cake: You'd just use a SQL statement to extract from the source and insert into the destination, right? What if you were copying production data to create test data and needed to alter the data to ensure that the destination data was secure to use in development? You may build a DTS or some other transfer mechanism, but if you do this across enough data it becomes time consuming to build data-scrubbing mechanisms each time you copy data. You could write an application to process and create the test data, but each time you use it on a different application you'll have to alter and create new algorithms.

Enter dynamic code compilation. Rather than continually writing a bunch of throwaway code, you could create an application that has some inner workings to transfer data and apply code snippets to alter the data during the transfer. The code snippets would represent each action you need to take on data. They would be stored as raw text in a database or some other location where they could easily be altered. The code snippets would be compiled and then applied to the data at the time of execution. This would allow you to have a database full of different code snippets that you easily could retrieve, modify, and apply without having to alter your fundamental application each time.

It's a rather complex scenario, but it should help you understand some of the possibilities. Now, look at how to make it happen.

CodeCompileUnit

To dynamically compile a class, start with a CodeCompileUnit from the System.CodeDom namespace. The CodeCompileUnit contains a program graph. To build up the code, you create a number of supporting objects and add them to the CodeCompileUnit instance. The objects represent common things you would have in your code if you were to build it at design time:

  • CodeNamespace—Represents the assigned namespace
  • CodeTypeDeclaration—Represents the type declaration
  • CodeMemberMethod—Represents a method

A HelloWorld Example

You can use the following sample code to generate code that contains a SayHello method that accepts a single parameter and returns a value. The value for the scriptBody method parameter becomes the body for the SayHello method. You'll contain your code to create the CodeCompileUnit within a static class that accepts parameters that influence the result:

public static CodeCompileUnit CreateExecutionClass(string typeNamespace,
                                                   string typeName,
                                                   string scriptBody)
{
   // Create the CodeCompileUnit to contain the code
   CodeCompileUnit ccu = new CodeCompileUnit();

   // Assign the desired namespace
   CodeNamespace cns = new CodeNamespace(typeNamespace);
   cns.Imports.Add(new CodeNamespaceImport("System"));
   ccu.Namespaces.Add(cns);

   // Create the new class declaration
   CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
   cns.Types.Add(parentClass);

   // Create the SayHello method that takes a parameter and has a
   // string return
   CodeMemberMethod method = new CodeMemberMethod();
   method.Name = "SayHello";
   method.Attributes = MemberAttributes.Public;
   CodeParameterDeclarationExpression arg = new
      CodeParameterDeclarationExpression(typeof(string),
                                         "inputMessage");
   method.Parameters.Add(arg);
   method.ReturnType = new CodeTypeReference(typeof(string));

   // Add the desired code to the method body
   CodeSnippetStatement methodBody =
      new CodeSnippetStatement(scriptBody);
   method.Statements.Add(methodBody);
   parentClass.Members.Add(method);

   return ccu;
}

CodeProvider

Now that you've created a CodeCompileUnit containing your code snippet, use it to generate the full source code to be compiled into your dynamic assembly. The following static method first calls the method from the previous example and then uses the CSharpCodeProvider to generate the full code:

public static string GenerateCode(string typeNamespace,
                                  string typeName,
                                  string scriptBody)
{
   // Call our prior method to create the CodeCompileUnit
   CodeCompileUnit ccu = CreateExecutionClass(typeNamespace,
                                              typeName, scriptBody);

   CSharpCodeProvider provider = new CSharpCodeProvider();
   CodeGeneratorOptions options = new CodeGeneratorOptions();
   options.BlankLinesBetweenMembers = false;
   options.IndentString = "\t";

   StringWriter sw = new StringWriter();
   try
   {
      provider.GenerateCodeFromCompileUnit(ccu, sw, options);
      sw.Flush();
   }
   finally
   {
      sw.Close();
   }

   return sw.GetStringBuilder().ToString();
}

As an example, calling the method GenerateCode with the input values of "CodeGuru.DynamicCode", "ScriptType", and "return inputMessage;" produces the following output:

//---------------------------------------------------------------
// <auto-generated>
//   This code was generated by a tool.
//   Runtime Version:2.0.50630.0
//
//   Changes to this file may cause incorrect behavior and will
//   be lost if the code is regenerated.
// </auto-generated>
//---------------------------------------------------------------

namespace CodeGuru.DynamicCode {
   using System;

   public class ScriptType {
      public virtual string SayHello(string inputMessage) {
         return inputMessage;
      }
   }
}

Discover Dynamic Code Compilation

Compile in Memory

The final step is to take the generated source code and compile it into an actual assembly. For this example, you'll contain the example to memory rather than a physical file. The actual compile action is performed through the specific language provider, which in this case will be the CSharpCodeProvider. You set any desired compile options and then compile the assembly from the source code.

The following sample code generates an assembly from your constructed code:

static Assembly CompileInMemory(string code)
{
   CSharpCodeProvider provider = new CSharpCodeProvider();

   CompilerParameters options = new CompilerParameters();
   options.IncludeDebugInformation = false;
   options.GenerateExecutable = false;
   options.GenerateInMemory = true;

   CompilerResults results =
      provider.CompileAssemblyFromSource(options, code);
   provider.Dispose();

   Assembly generatedAssembly = null;
   if (results.Errors.Count == 0)
   {
      generatedAssembly = results.CompiledAssembly;
   }

   return generatedAssembly;
}

A call such as Assembly a = CompileInMemory(GenerateCode(typeNamespace, typeName, "return inputMessage;")); would yield a new assembly. You could make subsequent calls with whatever method body you want in place of the "return inputMessage;" to create desired variants.

Creating an Instance

You've dynamically created an assembly and compiled it into memory. The next task is to create an instance of the class from the assembly. This is actually more complicated than it sounds. The assembly you've created exists in memory. No references to it exist, so you can't simply create a new instance because they wouldn't resolve. As a workaround, create a class to hold all of the compiled assemblies. You'll override the type resolution event so that when a type is requested you can use one of your types.

ExecutionHost Sample Code

The following code defines a class named ExecutionHost, which tracks all of your dynamically compiled assemblies:

using System;
using System.Collections;
using System.Reflection;

namespace CodeGuru.CodeDomSample
{
   class ExecutionHost
   {
      private Hashtable assemblies = null;
      public ExecutionHost()
      {
         assemblies = new Hashtable();

         // Respond to the type resolution event request
         // to intercept it and search our types
         AppDomain.CurrentDomain.TypeResolve += new
            ResolveEventHandler(CurrentDomain_TypeResolve);
      }

      private Assembly CurrentDomain_TypeResolve(object sender,
                                                 ResolveEventArgs args)
      {
         // Search our assemblies for the desired type
         Assembly a = null;
         if (assemblies.ContainsKey(args.Name))
         {
            a = (Assembly)assemblies[args.Name];
         }
         return a;
      }

      public void AddAssembly(string fullTypeName, Assembly a)
      {
         assemblies.Add(fullTypeName, a);
      }

      public string Execute(string typeFullName, string msg)
      {
         // Try to create the necessary type that triggers the event
         Type targetType = Type.GetType(typeFullName, true, true);
         object target =
            targetType.Assembly.CreateInstance(typeFullName);
         IExecutableModule m = (IExecutableModule)target;

         return m.SayHello(msg);
      }
   }
}

namespace CodeGuru.CodeDomSample
{
   public interface IExecutableModule
   {
      string SayHello(string inputMessage);
   }
}

public static CodeCompileUnit CreateExecutionClass(string typeNamespace,
                                                   string typeName,
                                                   string scriptBody)
{
   // Create the CodeCompileUnit to contain the code
   CodeCompileUnit ccu = new CodeCompileUnit();

   // Assign the desired namespace
   CodeNamespace cns = new CodeNamespace(typeNamespace);
   cns.Imports.Add(new CodeNamespaceImport("System"));
   ccu.Namespaces.Add(cns);
   // Create the class
   CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
   cns.Types.Add(parentClass);

   // NEW LINE - Add an implementation for the IExecutableModule
   // interface
   parentClass.BaseTypes.Add(typeof(CodeGuru.CodeDomSample.
                                    IExecutableModule));

   // Create the SayHello method that takes a parameter and has a
   // string return
   CodeMemberMethod method = new CodeMemberMethod();
   method.Name = "SayHello";
   method.Attributes = MemberAttributes.Public;
   CodeParameterDeclarationExpression arg = new
      CodeParameterDeclarationExpression(typeof(string),
                                         "inputMessage");
   method.Parameters.Add(arg);
   method.ReturnType = new CodeTypeReference(typeof(string));

   // Add the desired code to the method body
   CodeSnippetStatement methodBody = new
      CodeSnippetStatement(scriptBody);
   method.Statements.Add(methodBody);
   parentClass.Members.Add(method);

   return ccu;
}

Notice the Execute method. It uses reflection to create an instance of the desired type. This will trigger the _TypeResolve event and allow one of your assemblies to be returned if it has been added into the ExecutionHost through the AddAssembly method.

Also notice the added interface implementation in your dynamically generated code. Without it, you wouldn't know how to call the desired method. The IExecutableModule interface is provided along with an updated copy of the CreateExecutionClass method that adds the additional interface as a base class for your generated code.

Additionally, because you added an interface that now needs to be used within your CodeGuru.DynamicCode assembly, you must add a reference to the CodeGuru.CodeDomSample that contains the IExecutableModule declaration. See the updated copy of CompileInMemory below:

static Assembly CompileInMemory(string code)
{
   CSharpCodeProvider provider = new CSharpCodeProvider();

   CompilerParameters options = new CompilerParameters();
   options.IncludeDebugInformation = false;

options.GenerateExecutable = false; options.GenerateInMemory = true; // NEW LINE b add a reference to the necessary assembly options.ReferencedAssemblies.Add( "CodeGuru.CodeDomSample.exe"); CompilerResults results = provider.CompileAssemblyFromSource(options, code); provider.Dispose(); Assembly generatedAssembly = null; if (results.Errors.Count == 0) { generatedAssembly = results.CompiledAssembly; } return generatedAssembly; }

Now, you can use the following test code to test the end-to-end process of dynamically generating an assembly and then making a call to a method:

string typeNamespace = "CodeGuru.DynamicCode";
string typeName = "ScriptType" + Guid.NewGuid().ToString("N");
Assembly a = CompileInMemory(GenerateCode(typeNamespace, typeName,
                                          "return inputMessage;"));

ExecutionHost host = new ExecutionHost();
string fullTypeName = typeNamespace + "." + typeName;
host.AddAssembly(fullTypeName, a);
string test = host.Execute(fullTypeName, "Hello World!");

Using the Guid generates a unique object name each time you generate code.

Possible Enhancements

You've run through a very basic example to demonstrate a complex topic and the code required to accomplish the task. The added Guid in the type name ensures it is unique, so you can compile and use as many different types as you desire without clashing on names. Feel free to alter the "return inputMessage;" method body to whatever code you would like and experiment. You could change it so that all of the code for the method body is stored in a database and retrieved at runtime.

Future Columns

The next column has yet to be determined. If you have something in particular that you would like me to explain, please contact me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer (MCSD, MCSE, MCDBA) is a senior architect of .NET applications for large and mid-sized organizations. He is a technology leader with Crowe Chizek in Indianapolis, Indiana, specializing in architecture, design, and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the second year in a row. You can reach Mark at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Live Event Date: December 18, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this upcoming webcast …

Most Popular Programming Stories

More for Developers

RSS Feeds