Sunday, September 06, 2009

CodeDom and Expression Calculator (2)

In order to compile a snip of codes dynamically, I need to define a template in the class of ExpressionEvaluation so that it can be used as a base. The template contains several parameters which will be replaced (such as a class name, a data type and an expression). I defined a string with parameters enclosed by {} so that those parameters can be easily replaced by dynamic values (string.Format(template, parameters...)).

Here is the template of snip codes in the class:


using System;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

public class ExpressEvaluation { // class name
  private const string _InitalizeValueCode = "private {0} _value = {1};"; // valType, valExp
  private const string _ClassName = "_CalculatorWithFormula"// 0
  private const string _MethodAnswer = "Answer"// 1
  private const string _SourceCodeTemplate = @"
using System;
public class {} {{ // {{0}} Class name
  {2}
  public {} {1}()  // {{1}} method to be called to get result
  {{
    return _value;
  }}
}}";


  public int GetAnswer()         // method to get result as type
  {
    return _value;
  }
}


The key methods in the class are BuildCodes() and GetAnswerByBuildAndRunCodes():

private static string BuildCodes(
    string valueExp,
    string varType)
{
    string initializeValueCodes = string.Format(
            _InitalizeValueCode, varType, valueExp);

    string codes = string.Format(_SourceCodeTemplate,
        _ClassName, _MethodAnswer, initializeValueCodes, varType);

    return codes;
}

private static T GetAnswserByBuildAndRunCodes<T>(
    string sourceCodes) where T : struct
{
    object value = default(T);
    CompilerResults cr = GetCompiler(sourceCodes);

    var instance = cr.CompiledAssembly.CreateInstance(_ClassName);
    Type type = instance.GetType();
    MethodInfo m = type.GetMethod(_MethodAnswer);
    value = m.Invoke(instance, null);

    return (T)value;
}

Those two methods are very straightforward. In GetAnswserByBuildAndRunCodes(), a method GetCompiler() is called to get a C# CompilerResults object in the current context:

private static CompilerResults GetCompiler(string codes)
{
    CSharpCodeProvider codeProvider = new CSharpCodeProvider();

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

    foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
    {
        parameters.ReferencedAssemblies.Add(asm.Location);
    }

    return codeProvider.CompileAssemblyFromSource(parameters, codes);
}

Here is the complete source codes for download.

0 comments: