7

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.

majkinetor
  • 8,730
  • 9
  • 54
  • 72
  • 1
    Dynamic doesn't really help this scenario, dyanmic is about making a consistent syntax for 'when worlds collide'.. this is somewhat more like a repl and meta programming, which aparently will be in C# 5 (or whatever it gets called). – meandmycode Apr 17 '09 at 11:47
  • Regardless of which version number it is - in many ways this is a .NET (framework) feature, not a C# (language) feature. – Marc Gravell Apr 17 '09 at 11:53
  • Yes thats true, but you could say that a feature of C# 5 is a new managed compiler ;) hell technically it could be out of bound and not even named a 'System.' space. – meandmycode Apr 17 '09 at 11:59
  • @majkinetor - Re your comment on my post; sorry, but the interactive compiler is just a tangible example of the mono feature that is **exactly** what you describe. – Marc Gravell Apr 17 '09 at 12:07
  • Here's the underlying API: http://www.go-mono.com/docs/index.aspx?link=N:Mono.CSharp – Marc Gravell Apr 17 '09 at 12:09
  • VB does not allow for the execution of arbitrary strings – Joel Coehoorn Apr 17 '09 at 13:02

6 Answers6

13

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);
    }
  }
}
Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • I didn't check this yet, but it looks fantastic. Thanks for sharing. I am particuallry happy because I can provide only method body. – majkinetor Apr 17 '09 at 15:34
  • The following snippet throws a null reference exception if the assembly is not null: `if (_assembly == null) { Compile(); _dynamicCondition = ...; } return _dynamicCondition.Transform(functionName, p);`. The `_dynamicCondition = ...;` should be placed outside `if{}` braces. – Artemix Aug 03 '12 at 10:59
  • Also, `CompilerException` is not defined, so users of this code should define it themself. – Artemix Aug 03 '12 at 11:04
  • `Transform` method looks unimplemented. You cannot add a dynamic function/method that is callable using Transform. Anyway this classes are fantastic! :) – Artemix Aug 03 '12 at 11:15
11

There's no other way of executing arbitrary C# source code other than compiling it into assembly and then executing it. Anders Hejlsberg (architect of C#) announced plans to expose the C# compiler as a service (basically a set of CLR classes), so this might be of some help when this happens.

"Compiler as a Service" basically means that you can compile an arbitrary piece of code into Expression or, better yet, into an AST and generally get hold of internal compiler workings.

Anton Gogolev
  • 113,561
  • 39
  • 200
  • 288
  • 2
    The C# compiler is available in the Microsoft.CSharp namespace. Microsoft.CSharp.CSharpCodeProvider().CreateCompiler() – codeape Apr 17 '09 at 13:46
  • 2
    The ICodeCompiler returned from this method is nothing more than a wrapper around "csc.exe". "Compiler as a Service" basically means that you can compile an arbitrary piece of code into Expression or, better yet, into an AST and generally get hold of internal compiler workings. – Anton Gogolev Apr 17 '09 at 13:52
  • I think this counts as a roslyn update (9/2012): http://msdn.microsoft.com/en-us/vstudio/hh500769.aspx – felickz Nov 09 '12 at 20:39
4

At the moment, the CSharpCodeProvider (in the article you cite) is the only way in the MS .NET implementation. The "compiler as a service" is one of the .NET vFuture features, and provides exactly what you ask for. Mono 2.x already has something comparable, IIRC (as discussed here).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Compiler as a service is a .NET 4 feature? – meandmycode Apr 17 '09 at 11:49
  • Maybe not - I lose track. It was certainly shown at PDC, so I thought it was - but I'll edit to the more vague "vFuture". – Marc Gravell Apr 17 '09 at 11:50
  • Bah, got really excited then, I think at PDC they were showing a prototype managed compile (c# compiler in c#, epic!) that they were heavily investing in to become the next standard c# compiler sometime after .NET 4. – meandmycode Apr 17 '09 at 11:53
  • Nice thing that Mono has interactive compiler. It has nothing to do with question tho, although its good to know about it. This is probably answer on PowerShell. – majkinetor Apr 17 '09 at 12:03
  • Here's the underlying API: http://www.go-mono.com/docs/index.aspx?link=N:Mono.CSharp – Marc Gravell Apr 17 '09 at 12:08
  • Even now, December 2010, 18 months after Marc Gravell's recommendation for it, the Mono.CSharp compiler does only statements and expressions. It does not handle compiling classes, or generating AST for classes. Seems pretty limited, and stagnant. – Cheeso Dec 29 '10 at 09:32
  • @Cheeso - I think "recommendation" is stretching things a bit... I merely observed that it *exists* – Marc Gravell Dec 29 '10 at 09:50
  • Ahhhh, gotcha. Jumping to conclusions, I was. By the way, I came by here looking for something different - a tool that can generate an AST for a C# module. Just for everyone's edification, I found it in the NRefactory library, part of SharpDevelop. It works. – Cheeso Dec 30 '10 at 00:47
1

This has not much to do with dynamic features of C# 4.0. Rather, the enhancements of the managed compiler and exposing its data structures to managed code makes it so easy.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
0

Is it essential that the language in the string be C#?

I know that Java can execute Python and Ruby dynamically if you include the relevant Jars, and I can't see why someone wouldn't have thought to port these systems to C# and .NET.

JeeBee
  • 17,476
  • 5
  • 50
  • 60
0

You could dynamically create a XSLT document in memory that includes a c# extension method.

The actual transformation could be little more then passing parms to the extension method and return results.

But the cited article is probably easier to use....

Whats the problem with using that code?

Mesh
  • 6,262
  • 5
  • 34
  • 53