Как оценить Expression внутри ExpressionVisitor? - PullRequest
3 голосов
/ 07 января 2012

Мне нужно использовать ExpressionVisitor для анализа выражения перед его выполнением.Для моих нужд мне нужно оценить правильную часть выражения Divide, но я не знаю, как это сделать.Вот пример кода, который у меня есть:

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

Предположим, у меня есть этот код для оценки:

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

В функции VisitBinary я получу узел, который будет содержать левуюи правая часть операции деления.У меня вопрос, как я могу оценить значение, которое я получу в правой части операции?

Ответы [ 4 ]

3 голосов
/ 10 января 2012

Я думаю, что самая трудная часть этой проблемы связана с переменными. Поэтому я бы начал с замены переменных на константы. После этого вам просто нужно выполнить и обновить выражение.

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 голосов
/ 07 января 2012

Обычно вы можете использовать этот метод для оценки лямбда-выражения (и передачи):

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

    var compiled = lambda.Compile();

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

Однако в вашем случае это не сработает, потому что выражение, которое вы пытаетесь оценить, зависит от x, которое не может быть оценено, если вы не укажете для него конкретное значение, как предложил Wiktor.

Чтобы указать значение параметра, необходимо изменить метод следующим образом:

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
}

Эта версия метода, однако, должна принимать в качестве параметра объект ExpressionParameter, который представляет x в вашем выражении, чтобы он знал, что делать со значением, переданным DynamicInvoke().

Чтобы получить соответствующий ExpressionParameter объект, вам нужен доступ к корневому выражению, а не к одному из его узлов, поэтому я думаю, что было бы неловко делать это в посетителе.

1 голос
/ 28 февраля 2012

Я думаю, что @ w0lf находится на правильном пути.

Чтобы получить параметры из посетителя, вам нужно переопределить VisitLambda.Лучший способ сделать это - переопределить каждый доступный метод посетителя и передать параметры всем вашим методам.

Другой способ - сохранить последние параметры.На самом деле массив параметров будет одинаковым во всем лямбда-выражении.

Вот фрагмент кода, умножающий правую часть операции деления на два и заменяющий ее в исходном выражении, предполагая правую и левую частисторона типа 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);
    }
}
1 голос
/ 07 января 2012

Если я вас правильно понимаю, вы хотите вернуть результат посещения вашего выражения в виде модифицированного дерева выражений, в котором правые части делений будут каким-то образом оценены. Вы бы использовали Update метод BinaryExpression для замены правого узла своим значением:

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

В этом коде newRightExpression должен относиться к типу, который наследуется от Expression. Если правый узел оценивает, скажем, значение double, то вам нужно будет обернуть его в ConstantExpression:

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