Search code examples
c#.netexpression.net-4.5expression-trees

Create expression for a simple math formula


I have some fun with Expressions and a question appears: it throws an exception that I didn't suppose.

I have an input - simple math formula, for example 2*x+3, and I want to create an expression tree for it. So I write this code

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

but I get an error:

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

Please, advice, how can it be fixed? Here I have a single global parameter and referencing it so I have no idea, why it says this stuff.


Solution

  • Problem with your code is that you have two instances of x parameter. One of them is private static field that is used across expression generation, and second one is that you create and use in Lambda creation.

    If you have ParameterExpression, then you should use the same instance in expression and pass the same instance into Lambda generation, otherwise it will fail like in your example.

    it will work fine if you remove parameterExpression and will use private Parameter field like this:

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

    Working example in .NetFiddle - https://dotnetfiddle.net/Onw0Hy