1

我正在存儲一個表示向量之間的距離矩陣的二維數組,作爲Dictionary<DistanceCell, double>。我的DistanceCell的實現有兩個字符串字段,表示被比較的向量。當密鑰存在時,C#字典會拋出KeyNotFoundException

class DistanceCell 
{ 
    public string Group1 { get; private set; } 
    public string Group2 { get; private set; } 

    public DistanceCell(string group1, string group2) 
    { 
     if (group1 == null) 
     { 
      throw new ArgumentNullException("group1"); 
     } 

     if (group2 == null) 
     { 
      throw new ArgumentNullException("group2"); 
     } 

     this.Group1 = group1; 
     this.Group2 = group2; 
    } 
} 

由於我使用這個類的一個關鍵,我推翻Equals()GetHashCode()

public override bool Equals(object obj) 
{ 
    // False if the object is null 
    if (obj == null) 
    { 
     return false; 
    } 

    // Try casting to a DistanceCell. If it fails, return false; 
    DistanceCell cell = obj as DistanceCell; 
    if (cell == null) 
    { 
     return false; 
    } 

    return (this.Group1 == cell.Group1 && this.Group2 == cell.Group2) 
      || (this.Group1 == cell.Group2 && this.Group2 == cell.Group1); 
} 

public bool Equals(DistanceCell cell) 
{ 
    if (cell == null) 
    { 
     return false; 
    } 

    return (this.Group1 == cell.Group1 && this.Group2 == cell.Group2) 
      || (this.Group1 == cell.Group2 && this.Group2 == cell.Group1); 
} 

public static bool operator ==(DistanceCell a, DistanceCell b) 
{ 
    // If both are null, or both are same instance, return true. 
    if (System.Object.ReferenceEquals(a, b)) 
    { 
     return true; 
    } 

    // If either is null, return false. 
    // Cast a and b to objects to check for null to avoid calling this operator method 
    // and causing an infinite loop. 
    if ((object)a == null || (object)b == null) 
    { 
     return false; 
    } 

    return (a.Group1 == b.Group1 && a.Group2 == b.Group2) 
      || (a.Group1 == b.Group2 && a.Group2 == b.Group1); 
} 

public static bool operator !=(DistanceCell a, DistanceCell b) 
{ 
    return !(a == b); 
} 

public override int GetHashCode() 
{ 
    int hash; 
    unchecked 
    { 
     hash = Group1.GetHashCode() * Group2.GetHashCode(); 
    } 

    return hash; 
} 

正如你可以看到,的DistanceCell的要求之一是,Group1Group2是可以互換的。因此,對於兩個字符串xyDistanceCell("x", "y")必須等於DistanceCell("y", "x")。這就是爲什麼我用乘法實現GetHashCode(),因爲DistanceCell("x", "y").GetHashCode()必須等於DistanceCell("y", "x").GetHashCode()

我遇到的問題是它在大約90%的時間內正常工作,但在剩下的時間裏它會拋出KeyNotFoundExceptionNullReferenceException。前者是在從字典中獲得密鑰時拋出的,而後者是在我用foreach循環遍歷字典時檢索到的,然後它檢索一個爲空的密鑰,然後嘗試調用Equals()。我懷疑這與我的GetHashCode()實施中的錯誤有關,但我不積極。還要注意,由於算法的性質,當我檢查它時,不應該存在字典中不存在密鑰的情況。該算法在每次執行時採用相同的路徑。

更新

我只是想通知大家,這個問題是固定的。事實證明,它與我的Equals()或GetHashCode()實現無關。我做了一些大量的調試,發現我得到一個KeyNotFoundException的原因是因爲密鑰在字典中並不存在,這很奇怪,因爲我肯定它正在被添加。問題在於我將密鑰添加到帶有多個線程的字典中,並且據此,c#Dictionary類不是線程安全的。所以時間一定是完美的,一個Add()失敗了,因此密鑰從未被添加到字典中。我相信這也可以解釋foreach循環如何偶爾創建一個空鍵。來自多個線程的Add()必須已經混淆了字典中的一些內部結構並引入了一個空鍵。

感謝各位的幫助!我很抱歉,它最終導致了我的錯誤。

+0

您的'DistanceCell'類不是'sealed'。你確定沒有人從'DistanceCell'派生出來嗎?你的'Equals'方法不檢查'obj'或'cell'是否是'this'的多派生類型(反之亦然)。你應該包括這些支票,或者將你的班級標記爲「密封」。 –

+0

@Jeppe我沒有任何從'DistanceCell'派生的類,所以這不應該是一個問題,但我會將它標記爲'sealed'以保證安全。 – StrangerLoop

+0

我有這個問題,但實際的原因是我有兩種不同類型的空間,由複製粘貼代碼進出excel單元格造成的。 – will

回答

2

我相信什麼是對你的思念是這個答案 Using an object as a generic Dictionary key

你可能不是說明你實現IEquatable接口

+1

一個不**具有**來實現'IEquatable <>'。這樣做可能會很好,但是重寫'Equals'和'GetHashCode'就足夠了。 –

+0

我試過這樣做,但仍收到一個'KeyNotFoundException'。 – StrangerLoop

4

this blog post通過埃裏克利珀看看

它說的結果即使改變對象的內容,GetHashCode也不應該改變。原因是Dictionary正在使用桶來加快索引。一旦你改變GetHasCode結果,字典將無法爲你的對象找到合適的存儲桶,這可能會導致「未找到密鑰」

我可能是錯的,但它值得測試。

+0

這是我開始尋找的地方。 'GetHashCode()'應該__never__根據_mutable_字段進行計算。其實,除非你確定你在做什麼,否則我會避免壓倒它。默認值是一個自動遞增的值,應該可以正常工作。 – Erik

+0

這可能是正確的答案。即使屬性的設置者是「私人」的。問題是:'Group1'和'Group2'是否曾經在構造函數之外改變過?看起來好像提問者向我們展示了課程的完整代碼? –

+1

@SiLo我的'DistanceCell'對象不應該是可變的。它唯一的字段是兩個字符串'Group1'和'Group2',它們只在構造函數被調用時才被設置。 – StrangerLoop

0

對我來說你的代碼看起來正確。 (它可能是(微)優化的,但它看起來好像在所有情況下都是正確的。)你能提供一些代碼來創建和使用Dictionary<DistanceCell, double>,並且事情變糟糕嗎?問題可能在於你沒有向我們顯示的代碼。

+0

代碼非常長,在使用距離矩陣之前,有很多步驟可以計算矢量。我會做更多的測試,如果我找不到任何東西,我會發布可能會複製此問題的代碼。 – StrangerLoop

+1

你說得對,這裏的代碼是正確的。問題在於我從多個線程向字典中添加了一個密鑰,導致某個密鑰有時會丟失。感謝所有你幫助我解決這個問題的嘗試! – StrangerLoop

+0

啊,有人應該提到「'字典<,>'不是線程安全的」,但你發現一件好事(困難的方法)。 –