在运行时评估表达式

4

我有一个C#控制台应用程序项目。

我有一个逻辑表达式,存储在数据库中作为nvarchar。

例如,存储的表达式是:((34 > 0) || (US == ES)) && (4312 = 5691)

当我的应用程序运行时,我想检索表达式并评估它,以便结果为true或false。

如何在运行时实现?


这个问题中的答案可能会很有用。链接 - Bernard
+1 for NCalc:我在多个项目中使用它,效果很好。 - Catalin DICU
3个回答

7
这里有一个相当不寻常的解决方案,涉及到JScript:
  • Create a JScript class with the following code:

    public class JsMath {
        public static function Eval(expression:String) : Object {
            return eval(expression);
        }
    }
    
  • Compile it into a DLL:

    jsc /target:library /out:JsMath.dll JsMath.js
    
  • In your C# project, reference JsMath.dll and Microsoft.JScript.dll

  • Now you can use the Eval method as follows:

    string expression = "((34 > 0) || ('US' == 'ES')) && (4312 == 5691)";
    bool result = (bool)JsMath.Eval(expression);
    

好处:

  • 无需解析表达式,JScript引擎会为您完成
  • 不需要编译任意代码(如果用户输入的代码存在安全隐患,则可能成为一个大问题)
  • 只要遵循JScript语法,应该可以使用任何简单的数学或逻辑表达式

缺点:

  • 我所知道的没有传递变量的方法
  • 需要引用JScript程序集(在大多数情况下不是一个大问题,但我不确定此程序集是否可用于客户端配置文件或Silverlight)

1
+1 我自己已经成功地使用了这种技术。毫无疑问,这是一种不太为人知的技术。 - Tim Lloyd
我没有使用过它,但如果你想评估不受信任的字符串,你可能需要采取额外的预防措施来沙盒化你的JavaScript。 - CodesInChaos
@CodeInChaos: 实际上,我认为它很安全。您只能使用内置的方法和对象(据我所知是无害的),以及导入到封闭上下文中的命名空间。在这种情况下,上下文是JsMath.Eval方法,它不导入任何内容。唯一可能有害的内置对象是ActiveXObject,您可以轻松地在输入字符串中检测到它。 - Thomas Levesque
2
“在输入字符串中轻松检测它”我对此感觉不好。在大多数动态类型语言中,很容易规避这样的黑名单,例如使用eval。诀窍是以不作为字符串文字出现的形式创建恶意字符串,然后使用某些语言特性访问具有该名称的变量/类/...。 - CodesInChaos
1
实际上,您仍然可以做一些奇怪的事情,比如 JsMath.Eval("JsMath.Eval(expression)")(可预测地抛出异常 "Out of stack space")。 - Thomas Levesque
1
@CodeInChaos:你说得对...但是你也可以禁止在表达式中使用eval。无论如何,你需要特别小心所接受的输入... - Thomas Levesque

3
你可以将表达式解析成.NET的Expression类,并编译并运行它以获得结果。 这个类已经支持了你在示例中使用的所有逻辑操作,尽管它看起来有一些模糊(你在非常相似的方式下同时使用了 == = )。 但你需要自己编写解析器/转换器。

0

我已经编写了一个更紧凑、更高效的 K. Scott Allen 的 JScript 内联 Eval 调用程序,从这里 (https://odetocode.com/articles/80.aspx) 转载:

using System;
using System.CodeDom.Compiler;
using Microsoft.JScript;

class JS
{
    private delegate object EvalDelegate(String expr);
    private static EvalDelegate moEvalDelegate = null;

    public static object Eval(string expr)
    {
        return moEvalDelegate(expr);
    }

    public static T Eval<T>(string expr)
    {
        return (T)Eval(expr);
    }

    public static void Prepare()
    {
    }

    static JS()
    {
        const string csJScriptSource = @"package _{ class _{ static function __(e) : Object { return eval(e); }}}";
        var loParameters = new CompilerParameters() { GenerateInMemory = true };
        var loMethod = (new JScriptCodeProvider()).CompileAssemblyFromSource(loParameters, csJScriptSource).CompiledAssembly.GetType("_._").GetMethod("__");
        moEvalDelegate = (EvalDelegate)Delegate.CreateDelegate(typeof(EvalDelegate), loMethod);
    }
}

就像这样使用:

JS.Eval<Double>("1 + 4 + 5 / 99");

返回:

5.05050505050505

如果需要的话,您可以扩展它以传递变量值,例如传递名称和值的字典。静态类的第一次使用将需要100-200毫秒,之后几乎是即时的,并且不需要单独的DLL。如果您想要停止初始延迟,请调用JS.Prepare()进行预编译。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接