简体   繁体   English

如何评估ExpressionVisitor中的表达式?

[英]how to evaluate an Expression inside ExpressionVisitor?

I need to use ExpressionVisitor to analyse an Expression before executing it. 我需要在执行它之前使用ExpressionVisitor来分析Expression。 For my needs, i need to evaluate the right part of a Divide expression but i don't know how to do it. 根据我的需要,我需要评估Divide表达式的正确部分,但我不知道该怎么做。 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. 在VisitBinary函数中,我将收到一个节点,该节点将包含除法运算的左右部分。 My question is, how can i evaluate the value that i will get in the right part of the operation? 我的问题是,我如何评估我将在操作的正确部分获得的价值?

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. 之后,您只需执行并更新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;
    }
}

Usually you could use this method to evaluate a lambda expression (and pass ): 通常,您可以使用此方法来计算lambda表达式(并传递):

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. 但是,在您的情况下,这将无法工作,因为您尝试评估的表达式取决于x ,无法对其进行评估,除非您为其指定具体值,如Wiktor建议的那样。

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() . 但是,此版本的方法必须将ExpressionParameter对象作为参数,该对象表示表达式中的x ,以便它知道如何处理传递给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. 为了获得适当的ExpressionParameter对象,您需要访问根表达式,而不是其中一个节点,因此我想在访问者中进行访问会很尴尬。

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: 您将使用BinaryExpressionUpdate方法将右侧节点替换为您的值:

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 . 在此代码中, newRightExpression需要是继承自Expression的类型。 If the right node evaluates to, say, a double value, then you would need to wrap it in a ConstantExpression : 如果正确的节点评估为double值,那么您需要将它包装在ConstantExpression

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

I think @w0lf is on the correct path. 我认为@ w0lf是正确的道路。

To get the parameters from within the visitor, you need to override VisitLambda. 要从访问者中获取参数,您需要覆盖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. 实际上,参数数组在整个lambda表达式中都是相同的。

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. 这是一段代码,将除法运算的右侧乘以2并将其替换为原始表达式,假设右侧和左侧是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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM