【问题标题】:How can I evaluate a C# expression dynamically?如何动态评估 C# 表达式?
【发布时间】:2021-12-27 11:15:31
【问题描述】:

我想做相当于:

object result = Eval("1 + 3");
string now    = Eval("System.DateTime.Now().ToString()") as string

在 Biri s link 之后,我得到了这个 sn-p(修改以删除过时的方法 ICodeCompiler.CreateCompiler()

private object Eval(string sExpression)
{
    CSharpCodeProvider c = new CSharpCodeProvider();
    CompilerParameters cp = new CompilerParameters();

    cp.ReferencedAssemblies.Add("system.dll");

    cp.CompilerOptions = "/t:library";
    cp.GenerateInMemory = true;

    StringBuilder sb = new StringBuilder("");
    sb.Append("using System;\n");

    sb.Append("namespace CSCodeEvaler{ \n");
    sb.Append("public class CSCodeEvaler{ \n");
    sb.Append("public object EvalCode(){\n");
    sb.Append("return " + sExpression + "; \n");
    sb.Append("} \n");
    sb.Append("} \n");
    sb.Append("}\n");

    CompilerResults cr = c.CompileAssemblyFromSource(cp, sb.ToString());
    if (cr.Errors.Count > 0)
    {
        throw new InvalidExpressionException(
            string.Format("Error ({0}) evaluating: {1}", 
            cr.Errors[0].ErrorText, sExpression));
    }

    System.Reflection.Assembly a = cr.CompiledAssembly;
    object o = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");

    Type t = o.GetType();
    MethodInfo mi = t.GetMethod("EvalCode");

    object s = mi.Invoke(o, null);
    return s;

}  

【问题讨论】:

  • 请记住,除非您在单独的 AppDomain 中进行编译和执行,否则您可能会遇到内存问题。以这种方式生成的程序集无法卸载,但如果您在单独的 AppDomain 中创建程序集,则可以卸载 AppDomain 从而卸载生成的程序集。
  • 为什么这是必要的?我高度怀疑这是否是您想要的任何目的的最佳设计。很有可能还有其他机制可以产生您想要的结果,而无需打开在运行时评估代码 sn-ps 的大量蠕虫(出于安全性和可维护性)。
  • 我想要一种快速而肮脏的方法来将表达式验证添加到我编写的 DSL 中。我控制提供给评估程序的文件,因此永远不会打开蠕虫罐;)另外,我只允许一个表达式,不要添加任何命名空间/引用任何程序集。这也应该阻止我自己造成伤害!
  • 我在测试场景中这样做。我有一个程序集,它会在更新时更改版本号。我希望我的测试代码“后期绑定”到程序集 - 以便能够加载程序集并动态调用它。这是一个简单的反思问题。但是当我想实现一个在程序集中指定的接口时,它需要动态编译代码,因为接口本身是强命名的(带有版本号)。当我想调用 v1.3 时,我无法拥有实现 IFoo v1.2 的代码。动态编译解决了这个问题。
  • 我应该说,还有很多其他的场景。这不是主流的事情,但是动态代码编译有很多场景是有意义的。

标签: c# reflection eval


【解决方案1】:

老话题,但考虑到这是谷歌搜索时出现的第一个主题,这里有一个更新的解决方案。

您可以使用Roslyn's new Scripting API to evaluate expressions

如果您使用的是 NuGet,只需将依赖项添加到 Microsoft.CodeAnalysis.CSharp.Scripting。 要评估您提供的示例,很简单:

var result = CSharpScript.EvaluateAsync("1 + 3").Result;

这显然没有利用脚本引擎的异步功能。

您还可以按照您的意愿指定评估结果类型:

var now = CSharpScript.EvaluateAsync<string>("System.DateTime.Now.ToString()").Result;

要评估更高级的代码 sn-ps、传递参数、提供引用、命名空间等,请查看上面链接的 wiki。

【讨论】:

  • 我喜欢这个功能,而且效果很好,唯一的抱怨是它太慢了
  • 非常慢。添加 30 秒来评估简单的三级 40 表达式
  • @hungryMind 你不要只使用表面价值的EvaluateAsync。使用这种方法时需要考虑 3 件事。 1. 罗斯林热身。 2.表达式编译。 3. 表达式评估。前两个占用了您在此处看到的 40 秒的 99%。如果您缓存 'CSharpScrip.Create` 的结果并改用它,您将获得所请求字符串的本机性能。
【解决方案2】:

我编写了一个开源项目Dynamic Expresso,它可以将使用 C# 语法编写的文本表达式转换为委托(或表达式树)。文本表达式被解析并转换为Expression Trees,无需使用编译或反射。

你可以这样写:

var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");

var interpreter = new Interpreter()
                .SetVariable("service", new ServiceExample());

string expression = "x > 4 ? service.aMethod() : service.AnotherMethod()";

Lambda parsedExpression = interpreter.Parse(expression, 
                        new Parameter("x", typeof(int)));

parsedExpression.Invoke(5);

我的作品基于 Scott Gu 的文章http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

【讨论】:

【解决方案3】:

如果您特别想在自己的项目中调用代码和程序集,我建议您使用C# CodeDom CodeProvider

以下是我所知道的用于在 C# 中动态评估字符串表达式的最流行方法的列表。

微软解决方案

非微软解决方案(不是说有什么问题)

【讨论】:

  • 您似乎遗漏了一些选项(此处的其他答案中有些),例如 DataColumn.ExpressionDataTable.Compute 以及已弃用的 JScript。
【解决方案4】:
using System;
using Microsoft.JScript;
using Microsoft.JScript.Vsa;
using Convert = Microsoft.JScript.Convert;

namespace System
{
    public class MathEvaluator : INeedEngine
    {
        private VsaEngine vsaEngine;

        public virtual String Evaluate(string expr)
        {
            var engine = (INeedEngine)this;
            var result = Eval.JScriptEvaluate(expr, engine.GetEngine());

            return Convert.ToString(result, true);
        }

        VsaEngine INeedEngine.GetEngine()
        {
            vsaEngine = vsaEngine ?? VsaEngine.CreateEngineWithType(this.GetType().TypeHandle);
            return vsaEngine;
        }

        void INeedEngine.SetEngine(VsaEngine engine)
        {
            vsaEngine = engine;
        }
    }
}

【讨论】:

  • 有一个更好的 JavaScript 实现,叫做 Iron JS。它使用了诸如 Iron Python 之类的 DLR 等。Iron Js on NuGet。据我了解,Vsa 已过时
【解决方案5】:

这样做对性能有何影响?

我们使用基于上述内容的系统,其中每个 C# 脚本都被编译为内存中的程序集并在单独的 AppDomain 中执行。还没有缓存系统,所以脚本每次运行时都会重新编译。我做了一些简单的测试,一个非常简单的“Hello World”脚本在我的机器上编译大约需要 0.7 秒,包括从磁盘加载脚本。 0.7 秒对于脚本系统来说很好,但对于响应用户输入来说可能太慢了,在这种情况下,像 Flee 这样的专用解析器/编译器可能会更好。

using System;
public class Test
{
    static public void DoStuff( Scripting.IJob Job)
    {
        Console.WriteLine( "Heps" );
    }
}

【讨论】:

    【解决方案6】:

    看起来还有一种方法可以使用 RegEx 和 XPathNavigator 来评估表达式。我还没有机会测试它,但我有点喜欢它,因为它不需要在运行时编译代码或使用不可用的库。

    http://www.webtips.co.in/c/evaluate-function-in-c-net-as-eval-function-in-javascript.aspx

    我会尝试一下,然后告诉它是否有效。我也打算在 Silverlight 中尝试一下,但为时已晚,我现在快睡着了。

    【讨论】:

      【解决方案7】:

      虽然 C# 本身不支持 Eval 方法,但我有一个 C# eval 程序,它允许评估 C# 代码。它提供了在运行时评估 C# 代码并支持许多 C# 语句。事实上,此代码可用于任何 .NET 项目,但仅限于使用 C# 语法。请访问我的网站http://csharp-eval.com,了解更多详情。

      【讨论】:

        【解决方案8】:

        我刚刚用纯 C# 编写了一个类似的库 (Matheval)。 它允许评估字符串和数字表达式,如 excel 公式。

        using System;
        using org.matheval;
                            
        public class Program
        {
            public static void Main()
            {
                Expression expression = new Expression("IF(time>8, (HOUR_SALARY*8) + (HOUR_SALARY*1.25*(time-8)), HOUR_SALARY*time)");
                //bind variable
                expression.Bind("HOUR_SALARY", 10);
                expression.Bind("time", 9);
                //eval
                Decimal salary = expression.Eval<Decimal>();    
                Console.WriteLine(salary);
            }
        }
        

        【讨论】:

          【解决方案9】:

          这里有一段很好的代码 https://www.c-sharpcorner.com/article/codedom-calculator-evaluating-c-sharp-math-expressions-dynamica/

          下载它并使其成为可以在您的项目中引用的类库。这似乎非常快速和简单

          也许这会有所帮助!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-08
            • 1970-01-01
            • 1970-01-01
            • 2019-05-15
            • 2013-11-20
            相关资源
            最近更新 更多