2012-01-06 148 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