Popular Posts

Wednesday, June 29, 2011

Dynamically compile code at runtime

Compile Lambda or Code String to MethodInfo or Action or Func:
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace CodeCompiler
{
  public class CompilationException : Exception
  {
    public List<CompilerError> Errors { get; private set; }
    public CompilationException(CompilerErrorCollection errors) : base("Error compiling expression")
    {
      Errors = new List<CompilerError>();
      foreach (CompilerError error in errors)
        Errors.Add(error);
    }
  }

  public class Compiler
  {
    public List<string> ReferenceDLLs { get; private set; }
    public List<Assembly> ReferenceAssemblies { get; private set; }

    public Compiler()
    {
      ReferenceDLLs = new List<string> { "System.dll", "System.Core.dll", "System.Data.dll", "System.Data.DataSetExtensions.dll", "System.Xml.dll", "System.Xml.Linq.dll" };
      //ReferenceDLLs = AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic).Select(x => x.Location).ToList();
      ReferenceAssemblies = new List<Assembly>();
    }

    public Assembly CompileCSFile(string csFilePath)
    {
      using (StreamReader reader = new StreamReader(csFilePath))
        return compileString(reader.ReadToEnd());
    }

    private MethodInfo compileStringToMethodInfo(string code)
    {
      Assembly assembly = compileString(code);
      return assembly.GetType("FakeNamespace.FakeClass").GetMethod("MethodResult", BindingFlags.Static | BindingFlags.Public);
    }

    private Assembly compileString(string code)
    {
      CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
      CompilerResults result = compiler.CompileAssemblyFromSource(new CompilerParameters(ReferenceDLLs.Union(ReferenceAssemblies.ConvertAll(x => x.Location)).ToArray()), code);
      if (result.Errors.Count > 0)
        throw new CompilationException(result.Errors);
      return result.CompiledAssembly;
    }

    private List<string> defaultUsingStatements()
    {
      return new List<string>()
                       {
                            "using System;",
                            "using System.Collections.Generic;",
                            "using System.Linq;",
                            "using System.IO;"
                       };
    }

    private string surroundLambaWithFakeClass<X, Y>(string text, List<string> usingStatements = null)
    {
      usingStatements = usingStatements ?? defaultUsingStatements();
      string code = usingStatements.Aggregate((x, y) => x + "\r\n" + y) + @"

                namespace FakeNamespace
                {
                  public class FakeClass
                  {
                    public static CLASS2 MethodResult(CLASS1 param1)
                    {
                       Func<CLASS1, CLASS2> lambda = " + text + @";
                       return lambda(param1);
                    }
                  }
          }";

      code = code.Replace("CLASS1", typeof(X).ToString()).Replace("CLASS2", typeof(Y).ToString()).Replace("`1[", "<").Replace("]", ">");
      return code;
    }

    private string surroundLambaWithFakeClass<X>(string text, List<string> usingStatements = null)
    {
      usingStatements = usingStatements ?? defaultUsingStatements();
      string code = usingStatements.Aggregate((x, y) => x + "\r\n" + y) + @"

                namespace FakeNamespace
                {
                  public class FakeClass
                  {
                    public static void MethodResult(CLASS1 param1)
                    {
                       Action<CLASS1> lambda = " + text + @";
                       return lambda(param1);
                    }
                  }
          }";

      code = code.Replace("CLASS1", typeof(X).ToString()).Replace("`1[", "<").Replace("]", ">");
      return code;
    }

    private string surroundMethodWithFakeClass<X>(string text, List<string> usingStatements = null)
    {
      usingStatements = usingStatements ?? defaultUsingStatements();
      string code = usingStatements.Aggregate((x, y) => x + "\r\n" + y) + @"

                namespace FakeNamespace
                {
                  public class FakeClass
                  {
                    public static void MethodResult(CLASS1 param1)
                    {
                       " + text + @"
                    }
                  }
          }";

      code = code.Replace("CLASS1", typeof(X).ToString()).Replace("`1[", "<").Replace("]", ">");
      return code;
    }

    private string surroundMethodWithFakeClass<X, Y>(string text, List<string> usingStatements = null)
    {
      usingStatements = usingStatements ?? defaultUsingStatements();
      string code = usingStatements.Aggregate((x, y) => x + "\r\n" + y) + @"

                namespace FakeNamespace
                {
                  public class FakeClass
                  {
                    public static CLASS2 MethodResult(CLASS1 param1)
                    {
                       " + text + @"
                    }
                  }
          }";

      code = code.Replace("CLASS1", typeof(X).ToString()).Replace("CLASS2", typeof(Y).ToString()).Replace("`1[", "<").Replace("]", ">");
      return code;
    }

    public Func<X, Y> ConvertMethodStringToMethodInfo<X, Y>(string text, List<string> usingStatements = null)
    {
      string code = surroundMethodWithFakeClass<X, Y>(text, usingStatements);
      MethodInfo method = compileStringToMethodInfo(code);
      return x => (Y)method.Invoke(null, new object[] { x });
    }

    public Action<X> ConvertMethodStringToMethodInfo<X>(string text, List<string> usingStatements = null)
    {
      string code = surroundMethodWithFakeClass<X>(text, usingStatements);
      MethodInfo method = compileStringToMethodInfo(code);
      return x => method.Invoke(null, new object[] { x });
    }

    public Func<X, Y> ConvertLambaStringToMethodInfo<X, Y>(string text, List<string> usingStatements = null)
    {
      string code = surroundLambaWithFakeClass<X, Y>(text, usingStatements);
      MethodInfo methodInfo = compileStringToMethodInfo(code);
      Func<X, Y> result = x => (Y)methodInfo.Invoke(null, new object[] { x });
      return result;
    }

    public Action<X> ConvertLambaStringToMethodInfo<X>(string text, List<string> usingStatements = null)
    {
      string code = surroundLambaWithFakeClass<X>(text, usingStatements);
      MethodInfo methodInfo = compileStringToMethodInfo(code);
      return x => methodInfo.Invoke(null, new object[] { x });
    }

    public static string EscapeToProtectFromCodeInjection(string searchText)
    {
      return "\"" + searchText.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
    }
  }
}

Here's how to use it:
          Func<string, IEnumerable<string>> stringSplitFunction = Compiler.ConvertLambaStringToMethodInfo<String, IEnumerable<String>>("x => x.Split(new string[] { \",\" }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable()");
          IEnumerable<string> result = stringSplitFunction("val1,val2");

No comments:

Post a Comment