2011-01-07 81 views
2

我正在嘗試編寫一個簡單的生成器,它使用表達式樹來動態生成一個方法,將某個類型的實例的所有屬性與該類型的另一個實例的屬性進行比較。這適用於大多數屬性,如intstring,但對於DateTime?(推測其他可爲空值類型)失敗。使用表達式樹比較對象的所有屬性

的方法:

static Delegate GenerateComparer(Type type) 
{ 
    var left = Expression.Parameter(type, "left"); 
    var right = Expression.Parameter(type, "right"); 

    Expression result = null; 

    foreach (var p in type.GetProperties()) 
    { 
    var leftProperty = Expression.Property(left, p.Name); 
    var rightProperty = Expression.Property(right, p.Name); 

    var equals = p.PropertyType.GetMethod("Equals", new[] { p.PropertyType }); 

    var callEqualsOnLeft = Expression.Call(leftProperty, equals, rightProperty); 

    result = result != null ? (Expression)Expression.And(result, callEqualsOnLeft) : (Expression)callEqualsOnLeft; 
    } 

    var method = Expression.Lambda(result, left, right).Compile(); 

    return method; 

} 

在它失敗,DateTime?屬性:類型

表達 'System.Nullable`1 [System.DateTime的]' 不能被用於類型的參數「System.Object的」的方法「布爾等於(System.Object的)」

行,所以它發現期望的Equals過載。那麼爲什麼我不能通過DateTime?進去呢,因爲它可以兌換成object?如果我看看Nullable<T>,它的確可以覆蓋Equals(object o)

PS:我知道這是不是一個合適的發電機又因爲它不能與null值處理,但我會到達那個:)

UPDATE:伊拿克里斯的回答做了爲這個特定的問題工作,但最後我去了一個更簡單的方法,我認爲就足夠了:只需使用Expression.Equal即可。我認爲這涵蓋了99%的案例(不知道它是否可以處理覆蓋Equals而不是覆蓋==,但沒關係)。

回答

2

,如果你檢查的類型可爲空,此代碼它可能工作:

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)){} 

的代碼示例是從here
如果他們可以爲空,那麼你可以調用

Nullable.Equals<T>(T? n1, T? n2); 
+0

謝謝,該方法解決了我的問題,但最終我完全採用了不同的方法。 – stringargs 2011-01-10 10:11:48

0

在網絡搜索我可以使用的東西之後,我決定也自己實現它。我沒有使用表達樹。相反,我使用反射來掃描所有屬性,並使用ToString()來比較它們。如果該屬性是一個集合,它將比較集合中的每個元素是否相等。

這是代碼;

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Linq; 

namespace Utils 
{ 
    public class PropertyComparer<T> : IEqualityComparer<T> 
    { 
     public bool Equals(T x, T y) 
     { 
      IEnumerable<PropertyInfo> allProperties = typeof(T).GetProperties(); 
      foreach(PropertyInfo pi in allProperties) 
      { 
       if (pi.GetCustomAttributes<EqualityIrrelevantAttribute>().Any()) 
       { 
        continue; 
       } 

       object xProp = pi.GetValue(x); 
       object yProp = pi.GetValue(y); 

       if ((xProp == null) && (yProp == null)) 
       { 
        continue; 
       } 
       else if ((xProp == null) || (yProp == null)) 
       { 
        return false; 
       } 
       else if (xProp is ICollection) 
       { 
        if (!CollectionsEqual(xProp as ICollection, yProp as ICollection)) 
        { 
         return false; 
        } 
       } 

       if (xProp.ToString() != yProp.ToString()) 
       { 
        return false; 
       } 
      } 

      return true; 
     } 

     bool CollectionsEqual(ICollection left, ICollection right) 
     { 
      IEnumerator leftEnumerator = left.GetEnumerator(); 
      IEnumerator rightEnumerator = right.GetEnumerator(); 

      bool leftAdvanced = leftEnumerator.MoveNext(); 
      bool rightAdvanced = rightEnumerator.MoveNext(); 

      if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced)) 
      { 
       return false; 
      } 
      else if (!leftAdvanced && !rightAdvanced) 
      { 
       return true; 
      } 

      bool compareByClass = false; 
      object comparer = null; 
      MethodInfo equalsMethod = null; 

      // Inspect type first 
      object peek = leftEnumerator.Current; 
      Type valuesType = peek.GetType(); 
      if (valuesType.IsClass) 
      { 
       compareByClass = true; 
       Type comparerType = typeof(PropertyComparer<>).MakeGenericType(new Type[] { valuesType }); 
       equalsMethod = comparerType.GetMethod("Equals", new Type[] { valuesType, valuesType }); 
       comparer = Activator.CreateInstance(comparerType); 
      } 


      leftEnumerator.Reset(); 
      rightEnumerator.Reset(); 

      while (true) 
      { 
       leftAdvanced = leftEnumerator.MoveNext(); 
       rightAdvanced = rightEnumerator.MoveNext(); 

       if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced)) 
       { 
        return false; 
       } 
       else if (!leftAdvanced && !rightAdvanced) 
       { 
        return true; 
       } 

       object leftValue = leftEnumerator.Current; 
       object rightValue = rightEnumerator.Current; 

       if (compareByClass) 
       { 
        bool result = (bool)equalsMethod.Invoke(comparer, new object[] { leftValue, rightValue }); 
        if (!result) 
        { 
         return false; 
        } 
       } 
       else if (leftEnumerator.Current.ToString() != rightEnumerator.Current.ToString()) 
       { 
        return false; 
       } 

       // Continue looping 
      } 
     } 

     public int GetHashCode(T obj) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
} 

如果一個類的屬性本身就是一個類,那麼它將創建一個新的比較器來比較該類的屬性。您也可以選擇使用EqualityIrrelevantAttribute來標記要從比較中排除的特定屬性。它對我來說真的很好,我希望別人覺得它很有用。