【问题标题】:Execute a string in C# 4.0在 C# 4.0 中执行字符串
【发布时间】:2009-04-17 11:43:33
【问题描述】:

我想在 C# 中执行动态创建的字符串。我知道 VB 和 JScript.Net 可以做到这一点,甚至还有一个 way 可以在 C# 中使用它的程序集作为解决方法。我还发现这个 article 描述了如何做到这一点。

我今天阅读了有关 C# 4.0 特性的信息,这些特性使其更接近于将其作为主要特性之一的动态语言。那么,有谁知道 C# 4.0 是否包含一些允许字符串执行的内置功能,或任何其他方式来执行上述文章中描述的操作。

【问题讨论】:

  • Dynamic 并没有真正帮助这种情况,dyanmic 是关于为“当世界碰撞时”制定一致的语法。这有点像 repl 和元编程,显然将在 C# 5 (或其他任何名称)。
  • 无论它是哪个版本号 - 在许多方面,这是一个 .NET(框架)功能,而不是 C#(语言)功能。
  • 是的,没错,但你可以说 C# 5 的一个特性是一个新的托管编译器;) 从技术上讲,它可能已经越界,甚至没有命名为“系统”。空间。
  • @majkinetor - 回复您对我帖子的评论;抱歉,交互式编译器只是单声道功能的一个具体示例,正是您所描述的。

标签: c#


【解决方案1】:

这很容易做到。我构建了以下便利包装器。它们是结构化的,因此您可以从定义方法或表达式的源代码片段构造一个程序集,并使用 DynamicCodeManager 的辅助方法按名称调用它们。

代码根据调用进行按需编译。添加更多方法将导致下次调用时自动重新编译。

你只提供一个方法体。如果您不想返回值,则返回 null 并且不要费心使用 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);
    }
  }
}

【讨论】:

  • 我还没有检查这个,但它看起来很棒。感谢分享。我特别高兴,因为我只能提供方法体。
  • 如果程序集不为空,则以下 sn-p 将引发空引用异常:if (_assembly == null) { Compile(); _dynamicCondition = ...; } return _dynamicCondition.Transform(functionName, p);_dynamicCondition = ...; 应该放在 if{} 大括号之外。
  • 另外,CompilerException 没有定义,所以这段代码的用户应该自己定义。
  • Transform 方法看起来未实现。您不能添加可使用 Transform 调用的动态函数/方法。无论如何,这门课太棒了! :)
【解决方案2】:

除了将任意 C# 源代码编译成程序集然后执行之外,没有其他方法可以执行任意 C# 源代码。 Anders Hejlsberg(C# 架构师)宣布了将 C# 编译器作为服务(基本上是一组 CLR 类)公开的计划,因此当这种情况发生时,这可能会有所帮助。

“编译器即服务”基本上意味着您可以将任意一段代码编译为 Expression,或者更好的是,编译为 AST,并且通常可以掌握内部编译器的工作原理。

【讨论】:

  • C# 编译器在 Microsoft.CSharp 命名空间中可用。 Microsoft.CSharp.CSharpCodeProvider().CreateCompiler()
  • 从该方法返回的 ICodeCompiler 只不过是“csc.exe”的包装。 “编译器即服务”基本上意味着您可以将任意一段代码编译成 Expression,或者更好的是,编译成 AST,并且通常掌握内部编译器的工作原理。
  • 我认为这算是 roslyn 更新 (9/2012):msdn.microsoft.com/en-us/vstudio/hh500769.aspx
【解决方案3】:

目前,CSharpCodeProvider(在您引用的文章中)是 MS .NET 实现中的唯一方法。 “编译器即服务”是 .NET vFuture 的功能之一,可提供您所要求的内容。 Mono 2.x 已经有了类似的东西,IIRC (as discussed here)。

【讨论】:

  • 编译器即服务是 .NET 4 的功能吗?
  • 也许不是——我迷失了方向。它肯定在 PDC 上展示过,所以我认为是 - 但我将编辑为更模糊的“vFuture”。
  • 呸,当时真的很兴奋,我认为在 PDC 上,他们展示了原型托管编译(c# 中的 c# 编译器,史诗!),他们在之后的某个时间投入巨资成为下一个标准 c# 编译器.NET 4.
  • Mono 有交互式编译器的好处。它与问题无关,尽管很高兴知道它。这可能是 PowerShell 上的答案。
【解决方案4】:

这与 C# 4.0 的 dynamic 特性没有太大关系。相反,托管编译器的增强以及将其数据结构暴露给托管代码使其变得如此简单。

【讨论】:

    【解决方案5】:

    字符串中的语言必须是C#吗?

    我知道,如果包含相关的 Jars,Java 可以动态执行 Python 和 Ruby,我不明白为什么有人不考虑将这些系统移植到 C# 和 .NET。

    【讨论】:

    • 是的,否则我会使用 LuaNet
    【解决方案6】:

    您可以在内存中动态创建一个包含 c# 扩展方法的 XSLT 文档。

    实际的转换可能只是将 parms 传递给扩展方法并返回结果。

    但是引用的文章可能更容易使用....

    使用该代码有什么问题?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-17
      • 2013-02-19
      • 2021-01-22
      • 2015-10-14
      • 2011-11-10
      • 2017-03-29
      • 1970-01-01
      • 2012-11-10
      相关资源
      最近更新 更多