Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute a string in C# 4.0

Tags:

c#

I want to execute dynamically created string in C#. I know VB and JScript.Net can do it, and there is even a way to use its assembly in C# as a workaround. I also found this article describing how to do it.

I read today about C# 4.0 features which bring it closer to the dynamic languages that have this as one of the main features. So, does anybody know does C# 4.0 includes some built in features that allows for string execution, or any other way to do whats described in the article above.

like image 267
majkinetor Avatar asked Apr 17 '09 11:04

majkinetor


1 Answers

This is dead easy to do. I built the following convenience wrappers. They are structured so that you can construct an assembly from fragments of source code defining methods or expressions and invoke them by name using the helper methods of DynamicCodeManager.

The code is compiled on demand in response to invocation. Adding more methods will cause automatic recompilation on next invocation.

You provide only a method body. If you don't want to return a value then return null and don't bother to use the object returned by InvokeMethod.

If you use this in commercial code do me a favour and credit my work. The real jewel in this library is the invocation support. Getting the code to compile isn't the problem, it's invocation. It's quite tricky to get reflection to correctly match the method signature when you have a variable length parameter list. This is the reason for the existence of DynamicBase: the compiler resolves method binding to this explicitly declared base class giving us access to the right VMT. From there on in it all comes out in the wash.

I should also point out that this capability makes your desktop application vulnerable to script injection attacks. You should either take great care to vet the origin of script or reduce the trust level under which the generated assembly runs.

DynamicBase.cs

using System.Reflection;

namespace Dynamo
{
  public abstract class DynamicBase
  {
    public bool EvaluateCondition(string methodName, params object[] p)
    {
      methodName = string.Format("__dm_{0}", methodName);
      BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic;
      return (bool)GetType().InvokeMember(methodName, flags, null, this, p);
    }
    public object InvokeMethod(string methodName, params object[] p)
    {
      BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic;
      return GetType().InvokeMember(methodName, flags, null, this, p);
    }
    public double Transform(string functionName, params object[] p)
    {
      functionName = string.Format("__dm_{0}", functionName);
      BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic;
      return (double)GetType().InvokeMember(functionName, flags, null, this, p);
    }
  }
}

DynamicCodeManager.cs

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace Dynamo
{
  public static class DynamicCodeManager
  {
    #region internal statics and constants
    static Dictionary<string, string> _conditionSnippet = new Dictionary<string, string>();
    static Dictionary<string, string> _methodSnippet = new Dictionary<string, string>();
    static string CodeStart = "using System;\r\nusing System.Collections.Generic;\r\n//using System.Linq;\r\nusing System.Text;\r\nusing System.Data;\r\nusing System.Reflection;\r\nusing System.CodeDom.Compiler;\r\nusing Microsoft.CSharp;\r\nnamespace Dynamo\r\n{\r\n  public class Dynamic : DynamicBase\r\n  {\r\n";
    static string DynamicConditionPrefix = "__dm_";
    static string ConditionTemplate = "    bool {0}{1}(params object[] p) {{ return {2}; }}\r\n";
    static string MethodTemplate = "    object {0}(params object[] p) {{\r\n{1}\r\n    }}\r\n";
    static string CodeEnd = "  }\r\n}";
    static List<string> _references = new List<string>("System.dll,System.dll,System.Data.dll,System.Xml.dll,mscorlib.dll,System.Windows.Forms.dll".Split(new char[] { ',' }));
    static Assembly _assembly = null;
    #endregion

    public static Assembly Assembly { get { return DynamicCodeManager._assembly; } }

    #region manage snippets
    public static void Clear()
    {
      _methodSnippet.Clear();
      _conditionSnippet.Clear();
      _assembly = null;
    }
    public static void Clear(string name)
    {
      if (_conditionSnippet.ContainsKey(name))
      {
        _assembly = null;
        _conditionSnippet.Remove(name);
      }
      else if (_methodSnippet.ContainsKey(name))
      {
        _assembly = null;
        _methodSnippet.Remove(name);
      }
    }

    public static void AddCondition(string conditionName, string booleanExpression)
    {
      if (_conditionSnippet.ContainsKey(conditionName))
        throw new InvalidOperationException(string.Format("There is already a condition called '{0}'", conditionName));
      StringBuilder src = new StringBuilder(CodeStart);
      src.AppendFormat(ConditionTemplate, DynamicConditionPrefix, conditionName, booleanExpression);
      src.Append(CodeEnd);
      Compile(src.ToString()); //if the condition is invalid an exception will occur here
      _conditionSnippet[conditionName] = booleanExpression;
      _assembly = null;
    }

    public static void AddMethod(string methodName, string methodSource)
    {
      if (_methodSnippet.ContainsKey(methodName))
        throw new InvalidOperationException(string.Format("There is already a method called '{0}'", methodName));
      if (methodName.StartsWith(DynamicConditionPrefix))
        throw new InvalidOperationException(string.Format("'{0}' is not a valid method name because the '{1}' prefix is reserved for internal use with conditions", methodName, DynamicConditionPrefix));
      StringBuilder src = new StringBuilder(CodeStart);
      src.AppendFormat(MethodTemplate, methodName, methodSource);
      src.Append(CodeEnd);
      Trace.TraceError("SOURCE\r\n{0}", src);
      Compile(src.ToString()); //if the condition is invalid an exception will occur here
      _methodSnippet[methodName] = methodSource;
      _assembly = null;
    }
    #endregion

    #region use snippets
    public static object InvokeMethod(string methodName, params object[] p)
    {
      DynamicBase _dynamicMethod = null;
      if (_assembly == null)
      {
        Compile();
        _dynamicMethod = _assembly.CreateInstance("Dynamo.Dynamic") as DynamicBase;
      }
      return _dynamicMethod.InvokeMethod(methodName, p);
    }

    public static bool Evaluate(string conditionName, params object[] p)
    {
      DynamicBase _dynamicCondition = null;
      if (_assembly == null)
      {
        Compile();
        _dynamicCondition = _assembly.CreateInstance("Dynamo.Dynamic") as DynamicBase;
      }
      return _dynamicCondition.EvaluateCondition(conditionName, p);
    }

    public static double Transform(string functionName, params object[] p)
    {
      DynamicBase _dynamicCondition = null;
      if (_assembly == null)
      {
        Compile();
        _dynamicCondition = _assembly.CreateInstance("Dynamo.Dynamic") as DynamicBase;
      }
      return _dynamicCondition.Transform(functionName, p);
    }
    #endregion

    #region support routines
    public static string ProduceConditionName(Guid conditionId)
    {
      StringBuilder cn = new StringBuilder();
      foreach (char c in conditionId.ToString().ToCharArray()) if (char.IsLetterOrDigit(c)) cn.Append(c);
      string conditionName = cn.ToString();
      return string.Format("_dm_{0}",cn);
    }
    private static void Compile()
    {
      if (_assembly == null)
      {
        StringBuilder src = new StringBuilder(CodeStart);
        foreach (KeyValuePair<string, string> kvp in _conditionSnippet)
          src.AppendFormat(ConditionTemplate, DynamicConditionPrefix, kvp.Key, kvp.Value);
        foreach (KeyValuePair<string, string> kvp in _methodSnippet)
          src.AppendFormat(MethodTemplate, kvp.Key, kvp.Value);
        src.Append(CodeEnd);
        Trace.TraceError("SOURCE\r\n{0}", src);
        _assembly = Compile(src.ToString());
      }
    }
    private static Assembly Compile(string sourceCode)
    {
      CompilerParameters cp = new CompilerParameters();
      cp.ReferencedAssemblies.AddRange(_references.ToArray());
      cp.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().ManifestModule.FullyQualifiedName);
      cp.CompilerOptions = "/target:library /optimize";
      cp.GenerateExecutable = false;
      cp.GenerateInMemory = true;
      CompilerResults cr = (new CSharpCodeProvider()).CompileAssemblyFromSource(cp, sourceCode);
      if (cr.Errors.Count > 0) throw new CompilerException(cr.Errors);
      return cr.CompiledAssembly;
    }
    #endregion

    public static bool HasItem(string methodName)
    {
      return _conditionSnippet.ContainsKey(methodName) || _methodSnippet.ContainsKey(methodName);
    }
  }
}
like image 130
Peter Wone Avatar answered Oct 01 '22 02:10

Peter Wone