Выполнить строку в C # 4.0 - PullRequest
7 голосов
/ 17 апреля 2009

Я хочу выполнить динамически созданную строку в C #. Я знаю, что VB и JScript.Net могут это сделать, и есть даже способ использовать его сборку в C # как обходной путь. Я также нашел статью , описывающую, как это сделать.

Сегодня я читал о возможностях C # 4.0, которые приближают его к динамическим языкам, в которых это является одной из основных функций. Итак, кто-нибудь знает, включает ли C # 4.0 некоторые встроенные функции, которые позволяют выполнять строки, или любой другой способ сделать то, что описано в статье выше.

Ответы [ 6 ]

13 голосов
/ 17 апреля 2009

Это очень легко сделать. Я построил следующие удобные обертки. Они структурированы так, что вы можете создать сборку из фрагментов исходного кода, определяющих методы или выражения, и вызывать их по имени, используя вспомогательные методы DynamicCodeManager.

Код компилируется по требованию в ответ на вызов. Добавление дополнительных методов приведет к автоматической перекомпиляции при следующем вызове.

Вы предоставляете только тело метода. Если вы не хотите возвращать значение, верните ноль и не пытайтесь использовать объект, возвращенный InvokeMethod.

Если вы используете это в коммерческом коде, сделайте мне одолжение и поверьте моей работе. Настоящая жемчужина в этой библиотеке - поддержка вызовов. Получение кода для компиляции не проблема, это вызов. Это довольно сложно получить отражение, чтобы правильно соответствовать сигнатуре метода, когда у вас есть список параметров переменной длины. Это является причиной существования DynamicBase: компилятор разрешает привязку метода к этому явно объявленному базовому классу, предоставляя нам доступ к нужному VMT. С этого момента все выливается в умывальник.

Я также должен отметить, что эта возможность делает ваше настольное приложение уязвимым для атак с использованием скриптов. Вы должны либо очень тщательно проверить происхождение скрипта, либо снизить уровень доверия, при котором запускается сгенерированная сборка.

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);
    }
  }
}
11 голосов
/ 17 апреля 2009

Нет другого способа выполнить произвольный исходный код C #, кроме как скомпилировать его в сборку и затем выполнить его. Андерс Хейлсберг (архитектор C #) объявил о планах представить компилятор C # как службу (в основном набор классов CLR), так что это может помочь, когда это произойдет.

«Компилятор как услуга» в основном означает, что вы можете скомпилировать произвольный кусок кода в Expression или, еще лучше, в AST и, как правило, овладеть внутренней работой компилятора.

4 голосов
/ 17 апреля 2009

На данный момент CSharpCodeProvider (в цитируемой вами статье) является единственным способом реализации MS .NET. Компилятор как услуга - это одна из функций .NET vFuture, которая предоставляет именно то, что вы просите. Mono 2.x уже имеет что-то сопоставимое, IIRC (, как обсуждено здесь ).

1 голос
/ 17 апреля 2009

Это не имеет ничего общего с dynamic функциями C # 4.0. Скорее, усовершенствования управляемого компилятора и предоставления его структур данных управляемому коду делают это очень простым.

0 голосов
/ 17 апреля 2009

Вы можете динамически создать XSLT-документ в памяти, который включает метод расширения c #.

Фактическое преобразование может быть немного больше, чем передача parms в метод расширения и возврат результатов.

Но цитируемую статью, вероятно, проще использовать ....

В чем проблема с использованием этого кода?

0 голосов
/ 17 апреля 2009

Обязательно ли, чтобы язык в строке был C #?

Я знаю, что Java может динамически выполнять Python и Ruby, если вы включите соответствующие Jar-файлы, и я не понимаю, почему кто-то не подумал перенести эти системы на C # и .NET.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...