2012-01-06 160 views
3

我需要在执行它之前使用ExpressionVisitor来分析表达式。为了我的需要,我需要评估分数表达的正确部分,但我不知道如何去做。下面是我有一个示例代码:如何评估ExpressionVisitor中的表达式?

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功能,我会收到包含左侧和右侧部分节点分割操作。我的问题是,我如何评估我将在操作的正确部分获得的价值?

+1

这种评估的预期结果是什么?你会用具体的价值取代你的术语还是什么? – 2012-01-06 21:24:32

+0

增加一个简单的想法:递归案例:value = evaluate(node.left)+ node.value + evaluate(node.right)基本案例:if(isLeaf(node))return node.value; – Adrian 2012-01-06 21:32:58

回答

2

通常,您可以使用此方法来评估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对象,您需要访问根表达式,而不是其中的一个节点,所以我想在访问者中执行它会很尴尬。

1

如果我正确理解了你的话,你想返回你的表达式的结果是一个修改过的表达式树,其右边的分区以某种方式被评估。你会使用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)); 
3

我觉得这个问题最难的部分是处理的变量。所以我会开始通过替换常量的变量。之后你只需要执行和更新表达式。

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

我认为@ w0lf是在正确的路径上。

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

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

这是一段代码,将除法操作的右侧乘以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); 
    } 
} 
+0

“参数”是一个数组,但parameterValue是标量吗?如果lambda具有多个参数,这将引发异常。访问者构造函数应该可能接受一组参数值(可能用[params关键字](http://msdn.microsoft.com/en-us/library/w5zay9db.aspx)声明,以便它也可以在不明确的情况下被调用构建一个数组)。 – luksan 2012-03-04 01:28:21

+0

在问题的例子中,表达式的数据类型是Expression >。这保证了如果表达式是按照@ Alexandre的定义来构造的话,那么将会有一个带有DataInfo类型的单个参数。这是一个简单的表达式,即它不包含另一个lambda表达式。 – kerem 2012-03-05 07:55:34