为简单的数学公式创建表达式

时间:2015-05-21 12:01:57

标签: c# .net expression .net-4.5 expression-trees

我对表达式有一些乐趣并且出现了一个问题:它引发了一个我没想到的异常。

我有一个输入 - 简单的数学公式,例如2*x+3,我想为它创建一个表达式树。所以我写了这段代码

using System;
using System.Linq.Expressions;

namespace ConsoleApplication50
{
    class Program
    {
        static void Main()
        {
            string s = "3*x^2+2/5*x+4";
            Expression<Func<double, double>> expr = MathExpressionGenerator.GetExpression(s);
            Console.WriteLine(expr);

            var del = expr.Compile();

            Console.WriteLine(del(10));
        }


    }

    static class MathExpressionGenerator
    {
        public const string SupportedOps = "+-*/^";
        private static readonly ParameterExpression Parameter = Expression.Parameter(typeof(double), "x");

        public static Expression<Func<double, double>> GetExpression(string s)
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(double), "x");
            Expression result = GetExpressionInternal(s);
            return Expression.Lambda<Func<double, double>>(result, parameterExpression);
        }

        private static Expression GetExpressionInternal(string s)
        {
            double constant;
            if (s == "x")
                return Parameter;
            if (double.TryParse(s, out constant))
                return Expression.Constant(constant, typeof(double));
            foreach (char op in SupportedOps)
            {
                var split = s.Split(new[] { op }, StringSplitOptions.RemoveEmptyEntries);
                if (split.Length > 1)
                {
                    var expression = GetExpressionInternal(split[0]);
                    for (int i = 1; i < split.Length; i++)
                    {
                        expression = RunOp(expression, GetExpressionInternal(split[i]), op);
                    }
                    return expression;
                }
            }
            throw new NotImplementedException("never throws");
        }

        private static Expression RunOp(Expression a, Expression b, char op)
        {
            switch (op)
            {
                case '+':
                    return Expression.Add(a, b);
                case '-':
                    return Expression.Subtract(a, b);
                case '/':
                    return Expression.Divide(a, b);
                case '*':
                    return Expression.Multiply(a, b);
                case '^':
                    return Expression.Power(a, b);
            }
            throw new NotSupportedException();
        }
    }
}

但是我收到了错误:

  

未处理的异常:System.InvalidOperationException:变量'x'   输入“范围''引用的'Sys tem.Double',但未定义   在   System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpress   离子节点,VariableStorageKind存储)at   System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterEx   压力节点)   System.Linq.Expressions.ParameterExpression.Accept(ExpressionVisitor   访问或)...等等

请,建议,如何解决?在这里,我有一个全局参数并引用它,所以我不知道,为什么它会说这些东西。

2 个答案:

答案 0 :(得分:2)

您的代码存在问题,即您有两个x参数实例。其中一个是在表达式生成中使用的私有静态字段,第二个是在Lambda创建中创建和使用的。

如果你有ParameterExpression,那么你应该在表达式中使用相同的实例并将相同的实例传递给Lambda生成,否则它将像你的例子一样失败。

如果您移除parameterExpression并且将使用私有Parameter字段,它将正常工作:

public static Expression<Func<double, double>> GetExpression(string s)
{
    Expression result = GetExpressionInternal(s);
    return Expression.Lambda<Func<double, double>>(result, Parameter);
}

.NetFiddle中的工作示例 - https://dotnetfiddle.net/Onw0Hy

答案 1 :(得分:0)

static void Main(string[] args)
{
        var str = @"3*x^2+2/5*x+4";
        str = Transform(str);
        var param = Expression.Parameter(typeof (double), "x");
        var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] {param}, null, str);
        var exp10 = expression.Compile().DynamicInvoke(10);
        Console.WriteLine(exp10);
}
    public const string SupportedOps = "+-*/";//separators without ^
    private static string Transform(string expression)
    {
        //replace x^y with Math.Pow(x,y)
        var toBeReplaced = expression.Split(SupportedOps.ToCharArray()).Where(s => s.Contains("^"));
        var result = expression;
        return toBeReplaced.Aggregate(expression, (current, str) => current.Replace(str, string.Format("Math.Pow({0})", str.Replace('^', ','))));
        //OR
        //foreach (var str in toBeReplaced)
        //{
        //    result =result.Replace(str, string.Format("Math.Pow({0})", str.Replace('^', ',')));
        //}
        //return result;    
}