-->

how to evaluate an Expression inside ExpressionVis

2019-04-11 02:31发布

问题:

I need to use ExpressionVisitor to analyse an Expression before executing it. For my needs, i need to evaluate the right part of a Divide expression but i don't know how to do it. Here's a sample code that i have:

internal class RulesChecker : ExpressionVisitor
{
    private readonly object data;

    public RulesChecker(object data)
    {
        this.data = data;
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        if (node.NodeType == ExpressionType.Divide)
        {
            var rightExpression = node.Right;

            // compile the right expression and get his value            
        }

        return base.VisitBinary(node);
    }
}

Suppose that i have this code to evaluate:

Expression<Func<DataInfo, decimal?>> expression = x => x.A / (x.B + x.C);
var rulesChecker = new RulesChecker(data);
rulesChecker.Visit(expression);

In the VisitBinary function, i will receive a node that will contain the left and right part of the divide operation. My question is, how can i evaluate the value that i will get in the right part of the operation?

回答1:

I think the hardest part of this problem is dealing with the variables. So I would start by replacing the variables for constants. After that you just need to execute and update the Expression.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace WindowsFormsApplication1
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            var value1 = 1;
            var value2 = 2;
            var value3 = new { MyValue = 3 };
            var data = new DataInfo { A = 10, B = 1, C = -1 };

            Expression<Func<DataInfo, decimal?>> expression = x => x.A / (x.B + x.C) + (value1 + value2) + value3.MyValue;

            // create a list of variables that will be used when evaluating the expression
            var variables = new Dictionary<Type, object>();

            // add the root object
            variables.Add(data.GetType(), data);

            // find variables that are referenced in the expression
            var finder = new VariablesFinder(variables);
            finder.Visit(expression);

            // replace variables with ConstantExpressions
            var visitor = new VariableReplacer(variables);
            var newExpression = visitor.Visit(expression);

            var rulesChecker = new RulesChecker();
            var checkedExpression = rulesChecker.Visit(newExpression);
        }
    }

    internal class RulesChecker : ExpressionVisitor
    {
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node.NodeType == ExpressionType.Divide)
            {
                var rightBinaryExpression = node.Right as BinaryExpression;

                if (rightBinaryExpression != null)
                {
                    node = node.Update(node.Left, node.Conversion, this.Execute(rightBinaryExpression));
                }
            }

            return base.VisitBinary(node);
        }

        private Expression Execute(BinaryExpression node)
        {
            var lambda = Expression.Lambda(node);
            dynamic func = lambda.Compile();
            var result = func();

            return Expression.Constant(result, result.GetType());
        }
    }

    internal class VariableReplacer : ExpressionVisitor
    {
        private readonly Dictionary<Type, object> _variables;

        public VariableReplacer(Dictionary<Type, object> variables)
        {
            this._variables = variables;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            return this.HandleProperty(node) ??
                   this.HandleField(node) ??
                   node;
        }

        private Expression HandleField(MemberExpression memberExpression)
        {
            var fieldInfo = memberExpression.Member as FieldInfo;

            if (fieldInfo != null)
            {
                var value = fieldInfo.GetValue(this.GetVarialbe(fieldInfo));

                return Expression.Constant(value, fieldInfo.FieldType);
            }

            return null;
        }

        private Expression HandleProperty(MemberExpression memberExpression)
        {
            var propertyInfo = memberExpression.Member as PropertyInfo;

            if (propertyInfo != null)
            {
                var value = propertyInfo.GetValue(this.GetVarialbe(propertyInfo), null);

                return Expression.Constant(value, propertyInfo.PropertyType);
            }

            return null;
        }

        private object GetVarialbe(MemberInfo memberInfo)
        {
            return this._variables[memberInfo.DeclaringType];
        }
    }

    internal class VariablesFinder : ExpressionVisitor
    {
        private readonly Dictionary<Type, object> _variables;

        public VariablesFinder(Dictionary<Type, object> variables)
        {
            this._variables = variables;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            this.AddVariable(node.Type, node.Value);

            return base.VisitConstant(node);
        }

        private void AddVariable(Type type, object value)
        {
            if (type.IsPrimitive)
            {
                return;
            }

            if (this._variables.Keys.Contains(type))
            {
                return;
            }

            this._variables.Add(type, value);

            var fields = type.GetFields().Where(x => !x.FieldType.IsPrimitive).ToList();

            foreach (var field in fields)
            {
                this.AddVariable(field.FieldType, field.GetValue(value));
            }
        }
    }

    class DataInfo
    {
        public int A { get; set; }
        public int B { get; set; }
        public int C { get; set; }
        public int D;
    }
}


回答2:

Usually you could use this method to evaluate a lambda expression (and pass ):

protected object EvaluateExpression(Expression expression)
{
    var lambda = Expression.Lambda(expression);

    var compiled = lambda.Compile();

    var value = compiled.DynamicInvoke(null);
    return value;
}

However, in your case this won't work, because the expression you're trying to evaluate depends on x, which cannot be evaluated, unless you specify a concrete value for it, as Wiktor suggested.

In order to specify a value for the parameter, you need to modify the method as such:

protected static object EvaluateExpression(Expression expression, ParameterExpression parameterX)
{
    var lambda = Expression.Lambda(expression, parameterX);

    var compiled = lambda.Compile();

    return compiled.DynamicInvoke(5); 
            // 5 here is the actual parameter value. change it to whatever you wish
}

This version of the method, however, must take as a parameter the ExpressionParameter object that represents the x in your expression in order for it to know what to do with the value passed to DynamicInvoke().

In order to obtain the appropriate ExpressionParameter object you need access to the root expression, not to one of its nodes, so I guess it would be awkward to do it in a visitor.



回答3:

If I understand you correctly, you want to return the result of visiting your expression to be a modified expression tree that has the right-hand sides of divisions evaluated in some way. You would use the Update method of the BinaryExpression to replace the right node with your value:

protected override Expression VisitBinary(BinaryExpression node)
{
    if (node.NodeType == ExpressionType.Divide)
    {
        var rightExpression = node.Right;

        // compile the right expression and get his value            
        var newRightExpression = Evaluate(rightExpression);
        return node.Update(node.Left, node.Conversion, newRightExpression);
    }

    return base.VisitBinary(node);
}

In this code, newRightExpression needs to be of a type that inherits from Expression. If the right node evaluates to, say, a double value, then you would need to wrap it in a ConstantExpression:

double rightValue = EvaluateToDouble(rightExpression);
var newRightExpression = Expression.Constant(rightValue, typeof(double));


回答4:

I think @w0lf is on the correct path.

To get the parameters from within the visitor, you need to override VisitLambda. Best way to do it is to override every available method of the visitor, and pass the parameters to all of your methods.

Another method is to save the latest parameters. Actually, parameters array will be the same throughout a whole lambda expression.

Here is a piece of code, multiplying the right side of a division operation by two and replacing it in the original expression, assuming right side and left side are of type double.

class Program
{
    static void Main(string[] args)
    {
        Expression<Func<DateTime, double>> abc = v => 1.0d * v.Ticks / (v.Month + v.Minute);

        MyExpressionVisitor mev = new MyExpressionVisitor(DateTime.Now);
        var ret = mev.Visit(abc);
    }
}

internal class MyExpressionVisitor : ExpressionVisitor
{
    IEnumerable<ParameterExpression> _parameters = null;
    object _parameterValue = null;

    public MyExpressionVisitor(object valueOfParameter)
    {
        _parameterValue = valueOfParameter;
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        _parameters = node.Parameters;

        return base.VisitLambda<T>(node);
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        if (_parameters != null)
        {
            // Evaluate right node.
            var value = EvaluateExpression(node.Right, _parameters.ToArray(), _parameterValue);

            // substitute value with 2 * value.
            var newRight = Expression.Constant(value * 2);

            var ret = node.Update(node.Left, node.Conversion, newRight);

            return ret;
        }
        return base.VisitBinary(node);
    }

    protected double EvaluateExpression(Expression expression, ParameterExpression[] parameters, object parameterValue)
    {
        var lambda = Expression.Lambda(expression, parameters);

        var compiled = lambda.Compile();

        var value = compiled.DynamicInvoke(parameterValue);
        return Convert.ToDouble(value);
    }
}