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

Create expression for a simple math formula

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

我有一个输入 - 简单的数学公式,例如 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();
        }
    }
}

但是我得到一个错误:

Unhandled Exception: System.InvalidOperationException: variable 'x' of type 'Sys tem.Double' referenced from scope '', but it is not defined at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpress ion node, VariableStorageKind storage) at System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterEx pression node) at System.Linq.Expressions.ParameterExpression.Accept(ExpressionVisitor visit or) at ... and so on

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

您的代码存在问题,您有两个 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

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;    
}