实现的思路是,用运算符(加号和减号)切分字符串,在切分结果中去掉引导符($)得到所引用的对象的类型和名称,据此获得对象的值。后,通过调用第三方的函数计算替换后的表达式的值。

  这种方案的缺陷是明显的。首先,只考虑了部分运算符(加号和减号),其次,没有考虑括弧;再次,没有考虑对象名称中有运算符的问题;……设想一下处理了这些问题,实现会变得比这个复杂得多。

  老谭建议用正则表达式解析公式。

  三、利用正则表达式

  利用正则表达式,可以从公式中提取所关注的对象,称参与运算的对象为公式项。获取公式项的值后,用它替换公式中的内容。为此,首先要确定公式项的模式(Pattern)。

  3.1 公式项的模式

  在前面所给出的公式例子(($B'1车间一号表'+10-$G'一罐区二号罐'+$J'N-213号节点')*5))中,有三个运算对象,即三个公式项,它们的模式为:

  $[B|G|J]'.+'

  其中,$ 是原来的引导符,由于$是正则表达式中的保留字,需要转义;[B|G|J]表示B、G、J三者之一,.+表示一个或多个任意字符。

  由于我们要提取公式项中的内容,需要建立分组,方法同上一篇《在VS 2012中使用正则表达式 》介绍的一样,用括号将要提取的内容括起来。我们要提取的内容包括两方面:对象类型以及对象名称:

  $([B|G|J])'(.+)'

  使用正则表达式时要注意一个很隐蔽的问题。如果用上面的模式去匹配公式例子,我们本希望能匹配成功三次,匹配上的字符串分别是三个公式项,即:

  $B'1车间一号表'

  $G'一罐区二号罐'

  $J'N-213号节点'

  但实际上只匹配成功一次,匹配上的字符串是:

  $B'1车间一号表'+10-$G'一罐区二号罐'+$J'N-213号节点'

  其中,$([B|G|J]) 匹配到 “$B”,这没有问题。但 (.+) 匹配到 “1车间一号表'+10-$G'一罐区二号罐'+$J'N-213号节点”。出现这个问题的原因,是正则表达式用了贪婪匹配算法,它总是设法匹配到多的内容。阻止贪婪匹配的方法是在分组中加上问号运算符:

  $([B|G|J])'(.+?)'

  3.2 实现公式解析

  利用正则表达式求解公式的代码如下:

  
    private const string FormulaItemPattern = @"$([B|G|J])'(.+?)'";
 
    private double EvaluateFormula(string formula)
    {
        foreach (Match match in new Regex(FormulaItemPattern).Matches(formula))
        {
            var item = match.Groups[0].ToString();
            var type = match.Groups[1].ToString();
            var name = match.Groups[2].ToString();

            switch (type.ToLower())
            {
                case "b":
                    formula = formula.Replace(item, GetMeterValue(name));
                    break;
                case "g":
                    formula = formula.Replace(item, GetTankValue(name));
                    break;
                case "j":
                    formula = formula.Replace(item, GetNodeValue(name));
                    break;
            }
        }

        return EvaluateExpression(formula);
    }