2017-02-09 65 views
3

Expressions並不真正意義上的,但應該是。它們在一個小細節上有所不同。我對Expressions頗爲陌生,但我認爲即使對於經驗豐富的玩家來說,這也可能令人困惑。我重構了處理某些數據的代碼,以使Expression用作IQueryable.Where()的參數。據我所知,它在功能上是等同的。表達式幾乎是相同的,但其中一個不起作用

我這裏有原代碼,它運作良好,並生成功能完善的表達:

private Expression<Func<T, bool>> StringPropertyContains<T>(string propertyName, string value) 
{ 
    if (string.IsNullOrWhiteSpace(propertyName)) 
    { 
     throw new ArgumentNullException(nameof(propertyName)); 
    } 

    var param = Expression.Parameter(typeof(T)); 

    MemberExpression member = null; 
    if (propertyName.Contains('/')) 
    { 
     var splittedPropertyName = propertyName.Split('/'); 

     var propertyInfo = this.GetPropertyInfo(typeof(T), splittedPropertyName.First()); 
     member = Expression.MakeMemberAccess(param, propertyInfo); 

     for (int i = 1; i < splittedPropertyName.Length; i++) 
     { 
      if (propertyInfo.PropertyType.IsInterface) 
      { 
       //specifically for IActorWithExtraDetails -> reason to refactor 
       if (typeof(IActor).IsAssignableFrom(propertyInfo.PropertyType) && typeof(IActor).GetProperties().FirstOrDefault(pi => pi.Name.Equals(splittedPropertyName[i], StringComparison.OrdinalIgnoreCase)) != null) 
       { 
        propertyInfo = this.GetPropertyInfo(typeof(IActor), splittedPropertyName[i]); 
       } 
       else 
       { 
        propertyInfo = this.GetPropertyInfo(propertyInfo.PropertyType, splittedPropertyName[i]); 
       } 
      } 
      else 
      { 
       propertyInfo = this.GetPropertyInfo(propertyInfo.PropertyType, splittedPropertyName[i]); 
      } 

     } 

     member = Expression.MakeMemberAccess(member, propertyInfo); 
    } 
    else 
    { 
     var propertyInfo = this.GetPropertyInfo(typeof(T), propertyName); 

     member = Expression.MakeMemberAccess(param, propertyInfo); 
    } 

    var constant = Expression.Constant(value, typeof(string)); 
    var methodInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) }); 
    var body = Expression.Call(member, methodInfo, constant); 

    return Expression.Lambda<Func<T, bool>>(body, param); 
} 

這是它的外觀在IQueryableDebugView屬性:

.Lambda #Lambda2<System.Func`2[AccessManagement.Model.Application,System.Boolean]>(AccessManagement.Model.Application $var1) 
{ 
    .Call ($var1.Name).Contains("hive") 
} 

這裏是新的,重構的代碼轉移到自己的方法:

private Expression<Func<T, bool>> StringPropertyContains<T>(string propertyName, string value) 
{ 
    if (string.IsNullOrWhiteSpace(propertyName)) 
    { 
     throw new ArgumentNullException(nameof(propertyName)); 
    } 

    var param = Expression.Parameter(typeof(T)); 

    MemberExpression member = this.GetMemberExpression(typeof(T), propertyName.Trim('/').Split('/')); 

    var constant = Expression.Constant(value, typeof(string)); 
    var methodInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) }); 
    var body = Expression.Call(member, methodInfo, constant); 

    return Expression.Lambda<Func<T, bool>>(body, param); 
} 

    private MemberExpression GetMemberExpression(Type baseType, string[] path) 
    { 
     MemberExpression result = null; 
     Type type = baseType; 
     PropertyInfo propertyInfo = null; 

     foreach (string segment in path) 
     { 
      //if type is interface, just spray and pray 
      if (type.IsInterface) 
      { 
       propertyInfo = this.GetDescendantProperties(type) 
        .FirstOrDefault(pi => pi.Name.Equals(segment, StringComparison.OrdinalIgnoreCase)); 
      } 
      else 
      { 
       propertyInfo = this.GetPropertyInfo(type, segment); 
      } 

      if (propertyInfo == null) 
      { 
       throw new ArgumentNullException(nameof(propertyInfo)); 
      } 

      result = 
       result == null ? 
        Expression.MakeMemberAccess(Expression.Parameter(baseType), propertyInfo) : 
        Expression.MakeMemberAccess(result, propertyInfo); 
     } 

     return result; 
    } 

這是重構的方法表達看起來像在DebugView

.Lambda #Lambda2<System.Func`2[AccessManagement.Model.Application,System.Boolean]>(AccessManagement.Model.Application $var1) 
{ 
    .Call ($var2.Name).Contains("hive") 
} 

有一個區別是。如您所見,第二種情況下有$var2,而不是$var1。整個表達式樹中不存在此變量。我不知道爲什麼,但我敢打賭,這是問題,因爲其他一切都保持不變。只有其他區別在於第二種情況Expression處理是在member.RuntimeMethodInfo.base.m_cachedData(調試視圖路徑)中搜索的東西。

+1

第一代碼段利用了'param'表達的,而第二個忽略它。 –

+0

@LucasTrzesniewski對不起!可怕的錯誤,我忘了補充重構的表達式處理,編輯 - 不過'param'在第二片斷 – Qerts

+0

的第一種方法的最後一行用我想你是誤會我說什麼:)看到這個代碼'Expression.MakeMemberAccess( Expression.Parameter(基本類型)的PropertyInfo)','替換Expression.Parameter(BASETYPE)'一起'param',你傳遞給'Expression.Lambda'同一個參考。你在這裏創建第二無關參數,這就是'$ var2'從何而來。 –

回答

2

在你的第一個代碼段,你宣佈一個參數:

var param = Expression.Parameter(typeof(T)); 

這是用來作爲拉姆達參數,並在這裏的代碼被用於:

Expression.MakeMemberAccess(param, propertyInfo); 

(這是實際上被稱爲兩次)。所以代碼使用傳遞給lambda的參數。


在你的第二個代碼片段,您還在使用param作爲參數的拉姆達,但你不要在lambda身體的任何地方使用它。

你調用這個代替:

Expression.MakeMemberAccess(Expression.Parameter(baseType), propertyInfo) 

Expression.Parameter(baseType)創建第二個和無關變量,它永遠不會分配給它的實際值。你應該在這裏使用param參考。

這就是$var2從何而來。 $var1param參考。

考慮使用Expression.Parameter(Type, string)過載下一次,它可以讓你的名字你參數調試。推理起來會更容易。

相關問題