【问题标题】:Best and shortest way to evaluate mathematical expressions评估数学表达式的最佳和最短方法
【发布时间】:2010-11-29 02:33:44
【问题描述】:

计算表达式的算法有很多,例如:

  1. By Recursive Descent
  2. Shunting-yard algorithm
  3. Reverse Polish notation

有没有办法使用 C# .net 反射或其他现代 .net 技术来评估任何数学表达式?

【问题讨论】:

  • 不久前我问了一个类似的问题。你可能想看看其中的一些答案:stackoverflow.com/questions/234217/…
  • 您是否找到了链接到其余“静态/预编译”代码中使用的变量的方法?

标签: c# .net expression evaluation


【解决方案1】:

除了 Thomas 的回答之外,实际上可以直接从 C# 访问(已弃用的)JScript 库,这意味着您可以使用等效于 JScript 的 eval 函数。

using Microsoft.JScript;        // needs a reference to Microsoft.JScript.dll
using Microsoft.JScript.Vsa;    // needs a reference to Microsoft.Vsa.dll

// ...

string expr = "7 + (5 * 4)";
Console.WriteLine(JScriptEval(expr));    // displays 27

// ...

public static double JScriptEval(string expr)
{
    // error checking etc removed for brevity
    return double.Parse(Eval.JScriptEvaluate(expr, _engine).ToString());
}

private static readonly VsaEngine _engine = VsaEngine.CreateEngine();

【讨论】:

  • 遗憾的是它不支持插入符号 ^ 求幂。
【解决方案2】:

这当然是可能的。 CodeSnippetCompileUnit 类基本上就是这样做的。 我给你写了一些示例使用代码。您需要包含这些命名空间:

  • System.CodeDom.Compiler;
  • System.CodeDom;
  • Microsoft.CSharp;
  • System.Reflection;

代码如下:

string source = @"
class MyType
{
    public static int Evaluate(<!parameters!>)
    {
        return <!expression!>;
    }
}
";

string parameters = "int a, int b, int c";
string expression = "a + b * c";

string finalSource = source.Replace("<!parameters!>", parameters).Replace("<!expression!>", expression);

CodeSnippetCompileUnit compileUnit = new CodeSnippetCompileUnit(finalSource);
CodeDomProvider provider = new CSharpCodeProvider();

CompilerParameters parameters = new CompilerParameters();

CompilerResults results = provider.CompileAssemblyFromDom(parameters, compileUnit);

Type type = results.CompiledAssembly.GetType("MyType");
MethodInfo method = type.GetMethod("Evaluate");

// The first parameter is the instance to invoke the method on. Because our Evaluate method is static, we pass null.
int result = (int)method.Invoke(null, new object[] { 4, -3, 2 });

用任何东西替换“参数”和“表达式”,您就拥有了一个通用表达式评估器。

如果在 results.CompiledAssembly 中得到 FileNotFoundException,则 sn-p 编译失败。

您可能还想看看 System.CodeDom.CodeSnippetExpression 类。它用于更具体地读取表达式,但表达式本身无法编译,因此您需要使用更多 CodeDom 来围绕它构建一个工作类和方法。如果您希望能够以编程方式操作您正在生成的类,这将非常有用。 CodeSnippetCompileUnit 很适合一次生成整个工作类(例如更简单),但要操作它,您必须进行不方便的字符串操作。

【讨论】:

  • 为了记录,这个解决方案在使用 ncalc 上的性能是巨大的,我为一个绘图员测试了它,一些多变量函数需要超过 500 秒才能绘制出来,而我用了不到 5 秒的时间来绘制超过400,000点。很好的解决方案!
【解决方案3】:

虽然使用编译器服务是一种简单而有效的解决方案,但如果表达式由用户输入,则会引发严重的安全问题,因为它几乎可以执行任何事情

还有另一种非常简单但更安全的解决方案:利用 JScript Eval 函数。您只需要按照以下步骤操作:

创建一个名为 JsMath.js 的 js 文件:

class JsMath
{
    static function Eval(expression : String) : double
    {
        return eval(expression);
    };
}

编译成类库:

jsc /t:library JsMath.js

在你的 C# 项目中引用 JsMath 库,然后像这样使用它:

double result = JsMath.Eval(expression);

【讨论】:

  • 实际上可以直接从 C# 访问 eval 函数,而无需中间的 JScript 编译步骤。详情见我的回答。
  • 为了避免使用编译器服务的安全问题,我使用 ANTL 来预解析用户表达式并避免任何奇怪的输入。如果您正在寻找性能,eval() 函数可能不起作用。
【解决方案4】:

对我来说,Vici.Parser 工作得非常好:check it out here,它是迄今为止我发现的最灵活的表达式解析器。

(我们用它来设置“人类可读”的业务规则,数据由 SQL 服务器数据库提供)

提供示例并且开发人员提供了非常好的支持(查看网站的论坛)。

【讨论】:

  • @Roel - 链接已失效。
【解决方案5】:

ncalc 是最好的。您可以在 codeplex 中找到它,也可以在 nugget 中找到它。
NCalc 是 .NET 中的数学表达式求值器。 NCalc 可以解析任何表达式并评估结果,包括静态或动态参数和自定义函数。

【讨论】:

    【解决方案6】:

    我认为这是最好的方法。 Petar Repac's answer 太棒了。 使用 DataColumn 对象的“表达式”参数可以非常轻松地解决该主题:

    static double Evaluate(string expression)
    {
        var loDataTable = new DataTable();
        var loDataColumn = new DataColumn("Eval", typeof(double), expression);
        loDataTable.Columns.Add(loDataColumn);
        loDataTable.Rows.Add(0);
        return (double)(loDataTable.Rows[0]["Eval"]);
    }
    

    【讨论】:

      【解决方案7】:

      您可以使用Math-Expression-Evaluator 库,它实现了我所编写的Shunting Yard 算法。支持2.5+5.917.89-2.47+7.165/2/2+1.5*3+4.58等简单表达式,支持带括号的表达式(((9-6/2)*2-4)/2-6-1)/(2+24/(2+4))和带变量的表达式:

      var a = 6;
      var b = 4.32m;
      var c = 24.15m;
      var engine = new ExpressionEvaluator();
      engine.Evaluate("(((9-a/2)*2-b)/2-a-1)/(2+c/(2+4))", new { a, b, c});
      

      您也可以将参数作为命名变量传递:

      dynamic dynamicEngine = new ExpressionEvaluator();
      
      var a = 6;
      var b = 4.5m;
      var c = 2.6m;
      
      dynamicEngine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6);
      

      它支持 .Net Standard 2.0,因此可以从 .Net Core 以及 .Net Full Framework 项目中使用,并且没有任何外部依赖项。

      【讨论】:

        【解决方案8】:

        使用新的 Roslyn API 动态编译代码,并将程序集加载到 .net 核心项目中;

            string finalSource = ...;
            IEnumerable<Assembly> references = ...;
        
            var compilation = CSharpCompilation.Create("Dynamic",
                new[] { 
                    SyntaxFactory.ParseSyntaxTree(
                        finalSource,
                        CSharpParseOptions.Default
                            .WithLanguageVersion(LanguageVersion.Latest)
                    ) },
                references.Select(a => MetadataReference.CreateFromFile(a.Location)),
        
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
                    .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default)
            );
        
            using var ms = new MemoryStream();
            var e = compilation.Emit(ms);
            if (!e.Success)
                throw new Exception("Compilation failed");
            ms.Seek(0, SeekOrigin.Begin);
        
            var context = new AssemblyLoadContext(null, true);
            var assembly = context.LoadFromStream(ms);
        

        请注意,与您正在编译的源代码所需的任何其他类型一起。为了在同一进程中加载​​已编译的程序集,需要包含引用;

            AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == "netstandard").Single(),
            typeof(object).Assembly
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-12-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多