2017-04-05 105 views
1

我有一個從繼承自IEquatable的基類繼承的對象<>。到目前爲止這麼好,它適用於繼承相同基類的其他對象。但是當我使用「Attrbiutes」屬性時,我有類「RoomType」,在那裏似乎有問題。下面你會看到這些類和一個測試,我期望得到其他輸出。IEquatable對象上的LINQ.Distinct不起作用

當我註釋「SafeHashCode(Attributes)」返回預期結果時,我將問題縮小到了RoomType.GetHashCode()。

測試:

private static void QuickTest() 
    { 
     RoomType[] rooms = new RoomType[] { 
      new RoomType { 
       Attributes = new [] { "a", "b,"c"}, 
      }, 
      new RoomType 
      { 
       Attributes = new [] { "a", "b","c"}, 
      } 
     }; 

     List<RoomType> result = rooms.Distinct().ToList(); 
     //result contains 2 items, I was expecting 1 
    } 

RoomType:

public class RoomType : EntityBase 
{ 
    public string OriginalRoomCode { get; set; } 
    public Enum.RoomType RoomCode { get; set; } 
    public IEnumerable<string> Attributes { get; set; } 

    public override bool Equals(object obj) 
    { 
     RoomType other = obj as RoomType; 
     if (other != null) 
      return Equals(other); 
     return false; 
    } 

    public override bool Equals(EntityBase obj) 
    { 
     RoomType y = (RoomType)obj; 
     return SafeEqual(OriginalRoomCode, y.OriginalRoomCode) && 
      SafeEqual(RoomCode, y.RoomCode) && 
      SafeEqual(Attributes,y.Attributes); 
    } 

    public override int GetHashCode() 
    { 
     unchecked 
     { 
      return SafeHashCode(OriginalRoomCode)^
        SafeHashCode(RoomCode)^
        SafeHashCode(Attributes); 
     } 
    } 

    public override object Clone() 
    { 
     return new RoomType 
     { 
      RoomCode = (Enum.RoomType)SafeClone(RoomCode), 
      OriginalRoomCode = (string)SafeClone(OriginalRoomCode), 
      Attributes = (IEnumerable<string>)SafeClone(Attributes) 
     }; 
    } 
} 

EntityBase:

public abstract class EntityBase : IEquatable<EntityBase>, ICloneable 
{ 
    public bool SafeEqual<T>(T x, T y) 
    { 
     bool isXDefault = EqualityComparer<T>.Default.Equals(x, default(T)); 
     bool isYDefault = EqualityComparer<T>.Default.Equals(y, default(T)); 

     if (isXDefault && isYDefault) 
      return true; 
     if (isXDefault != isYDefault) 
      return false; 

     if (x is EntityBase) 
      return x.Equals(y); 

     IEnumerable<object> xEnumerable = x as IEnumerable<object>; 
     IEnumerable<object> yEnumerable = y as IEnumerable<object>; 

     if (xEnumerable != null && yEnumerable != null) 
     { 
      foreach (var yItem in yEnumerable) 
      { 
       bool match = false; 
       foreach (var xItem in xEnumerable) 
       { 
        if(SafeEqual(xItem, yItem)) 
        { 
         match = true; 
         break; 
        }       
       } 
       if (!match) 
        return false; 
      } 
      return true; 
     } 

     return x.Equals(y); 
    } 

    public int SafeHashCode<T>(T x) 
    { 
     if (EqualityComparer<T>.Default.Equals(x, default(T))) 
      return 0; 



     return x.GetHashCode(); 
    } 

    public object SafeClone<T>(T x) 
    { 
     //if x is null or default value 
     if (EqualityComparer<T>.Default.Equals(x, default(T))) 
      return default(T); 

     //if x is of type EntityBase call clone() 
     if (x is EntityBase) 
      return (x as EntityBase).Clone(); 

     //else the type is a default type return the value 
     return x; 
    } 

    public abstract bool Equals(EntityBase other); 
    public override abstract int GetHashCode(); 

    public abstract override bool Equals(object obj); 

    public abstract object Clone(); 
} 

更新 我能夠通過增加內SafeHashCode下面的代碼來修復它(T x)

IEnumerable<object> xEnumerable = x as IEnumerable<object>; 
     if (xEnumerable != null) 
      return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SafeHashCode(item)); 
+1

顯然'SaveHashCode'(你的意思是'Safe'?)不具有可枚舉正常工作(返回默認'GetHashCode'實現) –

+0

你說得對,我彪安全(更新它的時候了:) ) – BvdVen

+0

解決它的一種方法是插入'if(typeof(T)!= typeof(string)&& typeof(IEnumerable).IsAssignableFrom(typeof(T)))return 0;' –

回答

1

對於SaveEqual你有IEnumerable類型的比較自定義檢查 - 你去檢查從xEnumerable每個項目是否包含yEnumerable注意1:你也有一個bug - yEnumerable可以包含其他項目或它可以有重複的項目。

但對於SaveHashCode,您沒有對IEnumerable類型的自定義處理。您只需返回參數的哈希碼。即使數組包含相同的值,這也會爲不同的數組實例提供不同的結果。爲了解決這個問題,你應該根據收集的物品計算散列碼:

public int SaveHashCode<T>(T x) 
{ 
    if (EqualityComparer<T>.Default.Equals(x, default(T))) 
     return 0; 

    IEnumerable<object> xEnumerable = x as IEnumerable<object>; 
    if (xEnumerable != null) 
     return xEnumerable.Aggregate(17, (acc, item) => acc * 19 + SaveHashCode(item)); 

    return x.GetHashCode(); 
} 

注2使用XOR進行的hashCode計算爲您提供了另一個問題 - 零異或(你正在使用默認值,也可以是整數0的散列碼或布爾值false)不會修改結果。結果也不取決於訂單。即如果你有兩個數組:[0, 1, 2][2,0,1,0]。 XORing作爲@christophano建議會給你... 33。但是這些陣列完全不同。所以我建議你使用hashCode calcualtion based on prime numbers

+0

我實現了你的解決方案,它的工作原理,謝謝。你有第一個注意事項的建議嗎?我已經添加了 if(xEnumerable.Count()!= yEnumerable.Count()) return false; – BvdVen

+1

@BvdVen您可以檢查計數,然後壓縮兩個序列的相應位置比較項目'xEnumerable.Zip(yEnumerable,(xItem,yItem)=> SafeEqual(xItem,yItem))。所有(=的isEqual>的isEqual)' –

+1

那似乎有效。說實話,我從來沒有用過.Zip之前。謝謝! – BvdVen

2

問題是2個數組的哈希碼不會相同,即使內容可以被認爲是相等的。 而不是將數組傳遞給SafeHashCode獲取數組中每個成員的哈希碼。

public override int GetHashCode() 
{ 
    unchecked 
    { 
     return SafeHashCode(OriginalRoomCode)^
       SafeHashCode(RoomCode)^
       Attributes.Select(x => SafeGetHashCode(x)).Aggregate((seed, current) => seed^current); 
    } 
}