如何在 ExpressionVisitor 中计算表达式

本文关键字:计算 表达式 ExpressionVisitor | 更新日期: 2023-09-27 18:19:35

我需要在执行表达式之前使用 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 函数中,我将收到一个节点,该节点将包含除法操作的左侧和右侧部分。我的问题是,我如何评估我将在操作的正确部分获得的价值?

如何在 ExpressionVisitor 中计算表达式

我认为这个问题最困难的部分是处理变量。 所以我会从替换常量的变量开始。 之后,您只需要执行并更新表达式。

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

通常您可以使用此方法计算 lambda 表达式(并传递(:

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
}

但是,此版本的方法必须将表示表达式中x的 ExpressionParameter 对象作为参数,以便它知道如何处理传递给 DynamicInvoke() 的值。

为了获得适当的ExpressionParameter对象,您需要访问根表达式,而不是其节点之一,所以我想在访问者中执行此操作会很尴尬。

如果我理解正确,您希望将访问表达式的结果返回为修改后的表达式树,该树以某种方式计算了除法的右侧。您可以使用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);
}

在此代码中,newRightExpression必须是继承自 Expression 的类型。如果正确的节点计算结果为double值,则需要将其包装在ConstantExpression中:

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

我认为@w0lf走在正确的道路上。

要从访问者中获取参数,您需要覆盖 VisitLambda。最好的方法是覆盖访问者的每个可用方法,并将参数传递给所有方法。

另一种方法是保存最新参数。实际上,参数数组在整个 lambda 表达式中都是相同的。

这是一段代码,将除法运算的右侧乘以 2 并在原始表达式中替换它,假设右侧和左侧是双精度类型。

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