Being flexible: CodeDom + Reflection = Killer Combo

Component developers have to make decisions all the time about how to flexible their components need to be. They need to find the balance between flexibility and development duration: if they want to increase the flexibility, they probably need to spend more development time. How to implement the needed flexibility depends on the situation. In some cases it is needed that the components must execute code that will be written by developers that actually use these components, so that code is not yet available while designing the component. In other cases it may be required something changes depending on a setting, for example how Customers are displayed (first name + last name, last name + initial, …). Providing this kind of flexibility in your components can require a rather complex model to be able to determine what the ToString method of the Customer object needs to return. But this kind of flexibility could quite easy be accomplished if you only could put some code in a String, and let this code be evaluated by your component. Of course you’d want to avoid writing your own parser for such a String at all times, so what could be of any help? CodeDom and Reflection can be used for solving this kind of problems.

CodeDom can be used mainly for two goals: compilation at runtime and code generation. It is possible to define a class in memory using the CodeDom, and let it compile at runtime, so you can use the class you’ve constructed in your code, as a compiled class! But once you’ve defined that class using CodeDom, you can easily generated VB.NET or C# code for it. Reflection can be used to investigate and invoke objects at runtime. For example you can use Reflection to iterate through all properties or methods of a class, and invoke them when needed. In my opinion the CodeDom – Reflection combination is one on the great concepts in .NET. Let’s find out how to use them for the “flexibility problem”!

For example, you want to build a component that has a Customer class which’ ToString function can be determined when instantiating Customer instances. Let’s say the Customer class has two properties Name and Street. The flexible part of our Customer class is that we can determine how the ToString method will be implemented. For example:
Customer c = new Customer("customer.Name");
c.Name = "Jan";

If the ToString method of this customer object would be called, the string “Jan” would be returned. When we would instantiate the customer class as shown below, the result of the ToString method would be “JAN”, because of the ToUpper() function call:
Customer c = new Customer("customer.Name.ToUpper()");
c.Name = "Jan";

Notice that the same Customer class is used to obtain that result, and even complex functions can be used:
Customer c =
 new Customer("customer.Name + \" (\" + customer.Name.Length + \")\"");
c.Name = "Jan";

The result of invoking the ToString method would be “Jan (3)”. Notice that the quotes are escaped because you’ll have to put them in a String. Without the escaping that string looks like: customer.Name + “ (“ + customer.Name.Length + “)”

To obtain this behaviour the implementation of the ToString function on the Customer class is:
public override string ToString()
{
 Assembly ass;

 if(!AssemblyCache.ContainsKey(this.GetType().Name + _toString))
 {
  //Create the class definition using CodeDom
  CodeTypeDeclaration tempClass = new CodeTypeDeclaration("TempClass");
  tempClass.IsClass = true;
  CodeMemberMethod tempMethod = new CodeMemberMethod();
  tempMethod.Name = "GetValue";
  tempMethod.Attributes = MemberAttributes.Public;
  tempMethod.ReturnType = new CodeTypeReference(typeof(string));
  tempMethod.Parameters.Add(
   new CodeParameterDeclarationExpression(this.GetType(), "customer"));
  tempMethod.Statements.Add(
   new CodeMethodReturnStatement(
   new CodeSnippetExpression(this._toString)));
  tempClass.Members.Add(tempMethod);
 
  //Compile that class
  CodeCompileUnit unit = new CodeCompileUnit();
  CodeNamespace ns = new CodeNamespace("Temp");
  ns.Types.Add(tempClass);
  unit.Namespaces.Add(ns);
  CompilerParameters compilerParams = new CompilerParameters();
  compilerParams.GenerateInMemory = true;
  string assemblyFileName =
   Assembly.GetExecutingAssembly().GetName().CodeBase;
  compilerParams.ReferencedAssemblies.Add(
   assemblyFileName.Substring("
file:///".Length));
  ICodeCompiler compiler =
   new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();
  CompilerResults results =
   compiler.CompileAssemblyFromDom(compilerParams, unit);
  ass = results.CompiledAssembly;
  
  //Add compiled assembly to cache
  AssemblyCache.Add(ass, this.GetType().Name + _toString);
 }
 else
 {
  ass = AssemblyCache.GetAssembly(this.GetType().Name + _toString);
 }

 //Create an instance of the compiled class
 Type tempClassType = ass.GetType("Temp.TempClass");
 object tempClassInstance = Activator.CreateInstance(tempClassType);

 //Call the GetValue method of the TempClass instance
 //using Reflection.
 MethodInfo getValueMethod = tempClassType.GetMethod("GetValue");
 
 return (string)getValueMethod.Invoke(
  tempClassInstance,new object[] {this});
}

First the AssemblyCache is checked to find out if the required code is already compiled before. This cache is implemented using the Singleton design pattern, for the complete code, look at the end of this post. If the code was not yet compiled before, a new CodeTypeDeclaration instance is created that will represent a new class that will be compiled in memory. A new CodeMemberMethod is added to that class, representing the GetValue function. This function contains only one line: the return statement. This return statement executes the code that is passed in the constructor and stored in the _toString variable. At that point the class is finished, so a CodeCompileUnit is created, containing a Namespace that contains the temporary class. To compile that unit, a Compiler object is obtained from the CsharpCodeProvider. The resulting assembly is placed in the cache.

So now, we have a compiled assembly containing the code that we passed in the constructor. To actually use the temporary class, Reflection comes into play. First we need to retrieve a Type instance from that Assembly. Then a MethodInfo object is created, that represents the GetValue method. This MethodInfo object is used to invoke the method, and the result is used as return value. It’s that simple!

The AssemblyCache class is implemented like this:
public class AssemblyCache
{
 static System.Collections.Hashtable assemblies =
  new System.Collections.Hashtable();

 public static bool ContainsKey(string key)
 {
  return assemblies.ContainsKey(key);
 }

 public static void Add(Assembly ass, string key)
 {
  assemblies.Add(key, ass);
 }

 public static Assembly GetAssembly(string key)
 {
  return (Assembly)assemblies[key];
 }

 private AssemblyCache()
 {
  //Singleton!
 }
}

As you can see, CodeDom and Reflection can give developers a great ability to create very flexible components. If you need the complete source code of the example, contact me so I can send you the solution, or I’ll publish it so you could download it.

8 Comments

  • wow! good posting... I'll have to print this one out (& scratch head)...

    Ques: any thoughts on exception (error) handling) on this one?

  • I've come to love the power and flexibility that CodeDom/Reflection.Emit and Reflection can provide as well!

  • Error handling should be implemented (in my opinion) at, at least, 2 places:



    1) When invoking the GetValue method:

    return (string)getValueMethod.Invoke(

    tempClassInstance,new object[] {this});



    2) After compilation:

    CompilerResults results =

    compiler.CompileAssemblyFromDom(compilerParams, unit);

    When there are compilation errors, no exceptions are thrown. But you need the results.Errors collection to check if everything went ok.



    Jan

  • Jesse



    Ofcourse the example I showed is quite trival and is only for illustrating how the CodeDom and Reflection can be used. Altough I aggree with your statements, I think that there are limitations with implementing custom format providers. If my sample would be used in real life, the _toString would not be a property of each Customer instance, but it should be in a config. file or something like that, so only a very limited number of assemblies would be compiled in memory. Thanks for your thoughts!



    Jan

  • string assemblyFileName =

    Assembly.GetExecutingAssembly().GetName().CodeBase;

    compilerParams.ReferencedAssemblies.Add(

    assemblyFileName.Substring("file:///".Length));





    thanks dude.. the above thing made my life easier.. i had sorted out every thing except the above :)



    btw im working on a scripting engine in my application which lets the user to execute functions already built in my assembly :)



    cheers

  • Hello Jan:
    I have created a dynamic class out of an xml file.
    Is there a way to be able to access properties of the generated class as:


    tempClassType.Property1
    tempClassType.Property2

    Got my point? I want the user to be able to access the properties that I have added to the class without having to use GetProperty method

    Is there a way?
    Thanks.

  • I will recomend this site... Excelent work!!! May I use your palette at my site? Thanks!,I will recomend this site... Excelent work!!! May I use your palette at my site? Thanks!

  • I just like to say that your website is absoulutely brilliant!!! Do you think my sites too brilliant ;) ? Thanks!,I just like to say that your website is absoulutely brilliant!!! Do you think my sites too brilliant ;) ? Thanks!

Comments have been disabled for this content.