【问题标题】:operators as strings作为字符串的运算符
【发布时间】:2008-10-06 15:04:25
【问题描述】:

我需要计算在 C# 中作为字符串呈现给我的数学表达式。示例点头,但将字符串作为表达式的要点。

我需要评估然后填充一个 int。

C# 中没有 Eval() 和其他语言一样...

String myString = "3*4";

编辑:

我在VS2008上

尝试了 Microsoft.JScript。 = 其已弃用的方法(但仍符合 - 警告)

但是,我使用的 Microsoft.JScript dll 可以工作

公共对象 InvokeMember(字符串 名称、BindingFlags、invokeAttr、Binder binder, object target, object[] args);

抱怨缺少“;”去看看...

编辑 2

解决方案 - 是 codeDom 之一 - 因为没有安全问题,所以它起作用了 - 只有我会运行代码。非常感谢您的回复...

还有新龙书的链接太棒了

编辑 3

Matt dataTable.Compute() 也可以工作——对于安全意识来说甚至更好。 (注意参数检查)

【问题讨论】:

  • 您也可以查看Ncalc.codeplex.com,正如我在回答中建议的那样,由于我不明白的原因,它似乎已被删除。

标签: c#


【解决方案1】:

所有其他答案都可能矫枉过正。

如果你只需要简单的算术,就这样做。

        DataTable dummy = new DataTable();
        Console.WriteLine(dummy.Compute("15 / 3",string.Empty));

编辑:更多信息。查看 System.Data.DataColumn 类的 Expression 属性的 MSDN 文档。 “表达式语法”上的内容概述了除算术运算符之外您可以使用的命令列表。 (例如 IIF、LEN 等)。感谢大家投票支持我发布的第一个答案!

【讨论】:

  • 不错的小技巧。只是好奇:这对于错误的输入会有什么影响?
  • 它会抛出一个 System.Data.EvaluateException 或 System.Data.SyntaxErrorException 告诉你出了什么问题。
  • 哦,聪明! @OP:应该给这个答案标志:D
【解决方案2】:

在我看来,您有两个选择 - 使用表达式评估器或构造,编译 并即时运行 C# 代码。

我会使用表达式评估器库,因为您不必担心任何安全问题。也就是说,您可能无法在中等信任环境(例如大多数共享托管服务器)中使用代码生成。

以下是生成代码以评估表达式的示例: http://www.vbforums.com/showthread.php?t=397264

【讨论】:

  • 嗨 - dotMath 库似乎尚未从 workspace.gotdotnet.com 迁移 - 当他们过渡到 msdn 时...
  • 您也可以查看我在回答中建议的 Ncalc.codeplex.com,由于我不明白的原因,它似乎已被删除。
【解决方案3】:

几周前我在 C# 中做这个作为个人练习。

这是相当多的代码,并且在某些地方注释很差。但它确实适用于很多测试用例。

享受吧!

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace StackOverflow
{
    class Start
    {
        public static void Main(string[] args)
        {
            Evaluator ev;
            string variableValue, eq;
        Console.Write("Enter equation:  ");
        eq = Console.ReadLine();

        while (eq != "quit")
        {
            ev = new Evaluator(eq);
            foreach (Variable v in ev.Variables)
            {
                Console.Write(v.Name + " = ");
                variableValue = Console.ReadLine();
                ev.SetVariable(v.Name, Convert.ToDecimal(variableValue));
            }

            Console.WriteLine(ev.Evaluate());

            Console.Write("Enter equation:  ");
            eq = Console.ReadLine();
        }
    }
}

class EvalNode
{
    public virtual decimal Evaluate()
    {
        return decimal.Zero;
    }
}

class ValueNode : EvalNode
{
    decimal value;

    public ValueNode(decimal v)
    {
        value = v;
    }

    public override decimal Evaluate()
    {
        return value;
    }

    public override string ToString()
    {
        return value.ToString();
    }
}

class FunctionNode : EvalNode
{
    EvalNode lhs = new ValueNode(decimal.Zero);
    EvalNode rhs = new ValueNode(decimal.Zero);
    string op = "+";

    public string Op
    {
        get { return op; }
        set
        {
            op = value;
        }
    }

    internal EvalNode Rhs
    {
        get { return rhs; }
        set
        {
            rhs = value;
        }
    }

    internal EvalNode Lhs
    {
        get { return lhs; }
        set
        {
            lhs = value;
        }
    }

    public override decimal Evaluate()
    {
        decimal result = decimal.Zero;

        switch (op)
        {
            case "+":
                result = lhs.Evaluate() + rhs.Evaluate();
                break;

            case "-":
                result = lhs.Evaluate() - rhs.Evaluate();
                break;

            case "*":
                result = lhs.Evaluate() * rhs.Evaluate();
                break;

            case "/":
                result = lhs.Evaluate() / rhs.Evaluate();
                break;

            case "%":
                result = lhs.Evaluate() % rhs.Evaluate();
                break;

            case "^":
                double x = Convert.ToDouble(lhs.Evaluate());
                double y = Convert.ToDouble(rhs.Evaluate());

                result = Convert.ToDecimal(Math.Pow(x, y));
                break;

            case "!":
                result = Factorial(lhs.Evaluate());
                break;
        }

        return result;
    }

    private decimal Factorial(decimal factor)
    {
        if (factor < 1)
            return 1;

        return factor * Factorial(factor - 1);
    }

    public override string ToString()
    {
        return "(" + lhs.ToString() + " " + op + " " + rhs.ToString() + ")";
    }
}

public class Evaluator
{
    string equation = "";
    Dictionary<string, Variable> variables = new Dictionary<string, Variable>();

    public string Equation
    {
        get { return equation; }
        set { equation = value; }
    }

    public Variable[] Variables
    {
        get { return new List<Variable>(variables.Values).ToArray(); }
    }

    public void SetVariable(string name, decimal value)
    {
        if (variables.ContainsKey(name))
        {
            Variable x = variables[name];
            x.Value = value;
            variables[name] = x;
        }
    }

    public Evaluator(string equation)
    {
        this.equation = equation;
        SetVariables();
    }

    public decimal Evaluate()
    {
        return Evaluate(equation, new List<Variable>(variables.Values));
    }

    public decimal Evaluate(string text)
    {
        decimal result = decimal.Zero;
        equation = text;
        EvalNode parsed;

        equation = equation.Replace(" ", "");

        parsed = Parse(equation, "qx");

        if (parsed != null)
            result = parsed.Evaluate();

        return result;
    }

    public decimal Evaluate(string text, List<Variable> variables)
    {
        foreach (Variable v in variables)
        {
            text = text.Replace(v.Name, v.Value.ToString());
        }

        return Evaluate(text);
    }

    private static bool EquationHasVariables(string equation)
    {
        Regex letters = new Regex(@"[A-Za-z]");

        return letters.IsMatch(equation);
    }

    private void SetVariables()
    {
        Regex letters = new Regex(@"([A-Za-z]+)");
        Variable v;

        foreach (Match m in letters.Matches(equation, 0))
        {
            v = new Variable(m.Groups[1].Value, decimal.Zero);

            if (!variables.ContainsKey(v.Name))
            {
                variables.Add(v.Name, v);
            }
        }
    }

    #region Parse V2

    private Dictionary<string, string> parenthesesText = new Dictionary<string, string>();

    /*
     * 1.  All the text in first-level parentheses is replaced with replaceText plus an index value.
     *      (All nested parentheses are parsed in recursive calls)
     * 2.  The simple function is parsed given the order of operations (reverse priority to 
     *      keep the order of operations correct when evaluating).
     *      a.  Addition (+), subtraction (-)                   -> left to right
     *      b.  Multiplication (*), division (/), modulo (%)    -> left to right
     *      c.  Exponents (^)                                   -> right to left
     *      d.  Factorials (!)                                  -> left to right
     *      e.  No op (number, replaced parentheses) 
     * 3.  When an op is found, a two recursive calls are generated -- parsing the LHS and 
     *      parsing the RHS.
     * 4.  An EvalNode representing the root node of the evaluations tree is returned.
     * 
     * Ex.  3 + 5                   (3 + 5) * 8
     *           +                          *
     *          / \                        / \
     *         3   5                      +   8
     *                                   / \ 
     *      3 + 5 * 8                   3   5
     *            +
     *           / \
     *          3   *
     *             / \
     *            5   8
     */

    /// <summary>
    /// Parses the expression and returns the root node of a tree.
    /// </summary>
    /// <param name="eq">Equation to be parsed</param>
    /// <param name="replaceText">Text base that replaces text in parentheses</param>
    /// <returns></returns>
    private EvalNode Parse(string eq, string replaceText)
    {
        int randomKeyIndex = 0;

        eq = eq.Replace(" ", "");
        if (eq.Length == 0)
        {
            return new ValueNode(decimal.Zero);
        }

        int leftParentIndex = -1;
        int rightParentIndex = -1;
        SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);

        //remove extraneous outer parentheses
        while (leftParentIndex == 0 && rightParentIndex == eq.Length - 1)
        {
            eq = eq.Substring(1, eq.Length - 2);
            SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
        }

        //Pull out all expressions in parentheses
        replaceText = GetNextReplaceText(replaceText, randomKeyIndex);

        while (leftParentIndex != -1 && rightParentIndex != -1)
        {
            //replace the string with a random set of characters, stored extracted text in dictionary keyed on the random set of chars

            string p = eq.Substring(leftParentIndex, rightParentIndex - leftParentIndex + 1);
            eq = eq.Replace(p, replaceText);
            parenthesesText.Add(replaceText, p);

            leftParentIndex = 0;
            rightParentIndex = 0;

            replaceText = replaceText.Remove(replaceText.LastIndexOf(randomKeyIndex.ToString()));
            randomKeyIndex++;
            replaceText = GetNextReplaceText(replaceText, randomKeyIndex);

            SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
        }

        /*
         * Be sure to implement these operators in the function node class
         */
        char[] ops_order0 = new char[2] { '+', '-' };
        char[] ops_order1 = new char[3] { '*', '/', '%' };
        char[] ops_order2 = new char[1] { '^' };
        char[] ops_order3 = new char[1] { '!' };

        /*
         * In order to evaluate nodes LTR, the right-most node must be the root node
         * of the tree, which is why we find the last index of LTR ops.  The reverse 
         * is the case for RTL ops.
         */

        int order0Index = eq.LastIndexOfAny(ops_order0);

        if (order0Index > -1)
        {
            return CreateFunctionNode(eq, order0Index, replaceText + "0");
        }

        int order1Index = eq.LastIndexOfAny(ops_order1);

        if (order1Index > -1)
        {
            return CreateFunctionNode(eq, order1Index, replaceText + "0");
        }

        int order2Index = eq.IndexOfAny(ops_order2);

        if (order2Index > -1)
        {
            return CreateFunctionNode(eq, order2Index, replaceText + "0");
        }

        int order3Index = eq.LastIndexOfAny(ops_order3);

        if (order3Index > -1)
        {
            return CreateFunctionNode(eq, order3Index, replaceText + "0");
        }

        //no operators...
        eq = eq.Replace("(", "");
        eq = eq.Replace(")", "");

        if (char.IsLetter(eq[0]))
        {
            return Parse(parenthesesText[eq], replaceText + "0");
        }

        return new ValueNode(decimal.Parse(eq));
    }

    private string GetNextReplaceText(string replaceText, int randomKeyIndex)
    {
        while (parenthesesText.ContainsKey(replaceText))
        {
            replaceText = replaceText + randomKeyIndex.ToString();
        }
        return replaceText;
    }

    private EvalNode CreateFunctionNode(string eq, int index, string randomKey)
    {
        FunctionNode func = new FunctionNode();
        func.Op = eq[index].ToString();
        func.Lhs = Parse(eq.Substring(0, index), randomKey);
        func.Rhs = Parse(eq.Substring(index + 1), randomKey);

        return func;
    }

    #endregion

    /// <summary>
    /// Find the first set of parentheses
    /// </summary>
    /// <param name="eq"></param>
    /// <param name="leftParentIndex"></param>
    /// <param name="rightParentIndex"></param>
    private static void SetIndexes(string eq, ref int leftParentIndex, ref int rightParentIndex)
    {
        leftParentIndex = eq.IndexOf('(');
        rightParentIndex = eq.IndexOf(')');
        int tempIndex = eq.IndexOf('(', leftParentIndex + 1);

        while (tempIndex != -1 && tempIndex < rightParentIndex)
        {
            rightParentIndex = eq.IndexOf(')', rightParentIndex + 1);
            tempIndex = eq.IndexOf('(', tempIndex + 1);
        }
    }
}

public struct Variable
{
    public string Name;
    public decimal Value;

    public Variable(string n, decimal v)
    {
        Name = n;
        Value = v;
        }
    }
}

【讨论】:

  • 为什么不让EvalNode成为一个接口呢?仍然 +1。
  • 在最初的评估工作中,我只想让代码返回一些东西。代码的演变让我明白了发布的内容,你是对的,EvalNode 可以更改为接口。
  • 括号失败。例如:(1+3)+2
【解决方案4】:

您可以使用 jscript 解释器。 一篇很棒的文章在这里:http://www.odetocode.com/Articles/80.aspx

【讨论】:

    【解决方案5】:

    当您说“像其他语言一样”时,您应该说“像动态语言一样”。

    对于像 python、ruby 和许多解释语言这样的动态语言,Eval() 函数是一个自然元素。事实上,实现你自己的可能甚至是微不足道的。

    不过,.Net 的核心是一个静态、强类型、编译平台(至少在动态语言运行时获得更多支持之前)。这具有难以忽视的代码注入安全性和编译时类型检查等天然优势。但这意味着 Eval() 函数不太合适——它希望能够提前编译表达式。在这种平台中,通常还有其他更安全的方法来完成相同的任务。

    【讨论】:

    • Gambit Scheme 是一个动态的、强类型的、(可选的)编译平台,它确实有 eval。
    • 我相信你的意思是“像解释语言一样”
    • 在我的回答中加入了“解释”的建议——尽管不完全符合要求。
    【解决方案6】:

    查看Flee

    【讨论】:

      【解决方案7】:

      MS 有一个名为 Dynamic Query Library 的示例。它由 LINQ 团队提供,用于动态构建 LINQ 查询,例如: 暗查询 = Northwind.Products.Where("CategoryID=2") 您可以检查它是否提供基本的数学功能。

      【讨论】:

        【解决方案8】:

        在解释性语言中,您可能有机会使用解释器评估字符串。在 C# 中,您需要一个用于编写字符串的语言(数学表达式的语言)的解析器。这是一个不平凡的练习。如果您想这样做,请使用递归下降解析器。 “Dragon Book”的前几章(编译器:Aho、Sethi 和 Ullman 的设计等 - 1977 年第 1 版或 2007 年第 2 版)很好地解释了您需要做什么。

        另一种方法可能是在您的项目中包含一个用 perl 编写的组件,该组件现在应该可用于 .NET,并使用 perl 进行评估。

        【讨论】:

          【解决方案9】:

          在计算表达式时是否需要访问其他变量的值?

          【讨论】:

            【解决方案10】:

            jscript interpreter 可以做到,或者如果表达式很简单,您也可以编写自己的解析器(注意,它会很快变得复杂)。

            我很确定 C# 中没有直接的“Eval(string)”方法,因为它没有被解释。

            请记住,尽管代码解释会受到代码注入的影响,但要格外小心 :)

            【讨论】:

              【解决方案11】:

              经过一番谷歌搜索,我发现可以使用 CodeDom 动态创建和编译代码。 (见tutorial)。

              我个人认为这种方法不是一个好主意,因为用户可以输入他想要的任何代码,但这可能是一个需要探索的领域(例如,只验证输入,只允许数字和简单的数学运算)。

              【讨论】:

                【解决方案12】:

                其他一些建议:

                • Mono 2.0(今天发布)有一个 eval 方法。
                • 您可以轻松地在 boo 中编写一个特定的小型域。
                • 您可以创建一个老式的递归下降 EBNF 解析器。

                【讨论】:

                  【解决方案13】:

                  我已经在我的网站上发布了一个超紧凑(1 类,很小。

                  【讨论】:

                    猜你喜欢
                    • 2011-08-12
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-03-24
                    相关资源
                    最近更新 更多