C# Dictionary.ContainsKey()釋懝

今天在使用C#中的泛型集合Dictionary<TKey,TValue>時,發現它的ContainsKey()竟然不能使用我自訂的Key類別所覆寫的Equals()函式。這點著實令我感到不可思議。

ContainsKey()這個函式我已經使用很久了,可這還是第一次發現ContainsKey()傳回來的結果,令我訝異。

我想要的很簡單,就是我有一個資料型別,而我另外定義一個跟這個資料型別相互配合的Key類別。我想要將相同Key值的資料群組起來。於是我宣告了以下這樣的Dictionary<>:

Dictionary<TKey,TValue> dic = new Dictionary<Tkey, TValue>();

接著我就依續為每筆資料產生它對應的Key物件,將它放到這個Dictionary中。當然我的Key類別有覆寫了Equals()函式,而且我用ContainsKey()函式來判斷是否有相同Key的資料存在這個Dictionary中。可是當程式執行完後,發現沒有一筆資料的Key是相同的。我確認過資料,應該是會有Key是相同的才對。此時的我開始感覺不妙。難道Dictionary不再是我所認識的那個Dictionary了嗎!!

於是我開始查詢網路上相關的資料,有人提到在MSDN中有相關的說明。看了之後才恍然大悟,原來是這麼一回事啊。

IEqualityComparer

首先來看一段程式碼,這是我一開始以為可以正確地讓Dictionary<>.ContainsKey()為我找到相同Key值的寫法:

class MyKey
{
public int ID;
}
static void Test1()
{
Dictionary dicTest = new Dictionary();
MyKey key1 = new MyKey();
key1.ID = 10;

dicTest.Add(key1, "Test");
MyKey key2 = new MyKey();
key2.ID = 10;
if (dicTest.ContainsKey(key2))
{
Console.WriteLine("Found key");
}
else
{
Console.WriteLine("No key");
}
}

如同大家所看見的,我只是單純地用一個類別MyKey來作為我的Dictionary的TKey型別。而裏面只定義一個整數型別ID。在Test1()中,就像一般人會寫的一樣,我加入一個Key/Value的對應,後續再新產生一個MyKey物件,且設定它的ID跟前一個MyKey物件的ID一樣,結果,用Dictionary<>.ContainsKey()查詢,它傳回false。結果就是顯示:No key了。

如同MSDN中對Dictionary<>的說明一樣的,如果你在宣告一個Dictionary<>物件時,而未在建構子中提供實作IEqualityComparer介面的相關物件的話,那Dictionary<>預設會使用IEqualityComparer<TKey>.Default這個IEqualityComparer實作類別。而在IEqualityComparer<TKey>.Default中,它預設就是用物件的reference來進行比對。所以雖然你有相同的資訊的物件(如上面範例一中的key1及key2,它們都有相同的ID值),但由於reference值不同,所以就不被視為是同一個Key。也就在ContainsKey()中傳回false了。

IEqutable


如果你想在使用預設的IEqualityComparer<TKey>.Default下也能正確地進行你自訂Key類別的比對的話,那您就必須實作IEqutable介面。如下面的程式碼所示的:

class YourKey : IEquatable
{
public int ID;
#region IEquatable 成員
public bool Equals(YourKey other)
{
return this.ID == other.ID;
}
#endregion
}

在此,我們另外定義了一個YourKey類別,它跟MyKey類別一樣,都只有一個整數型別的ID值。不過,它實作了IEqutable的介面,並覆寫了Equals()這個函式。在Equals()函式中,單純地只是用ID來進行比對。

於是我們開始改用YourKey來取代MyKey,執行後呢,結果還是一樣。Dictionary<>.ContainsKey()函式還是沒有使用到我們實作且覆寫的Equals()函式。這是怎麼一回事呢?

原來Dictionary<>一開始是用HashCode來群組的,所以它會先依HashCode來決定某些 TKey的物件是否是同一群。如果是同一群時,再呼叫它的IEqutable::Equals()函式來比較。

所以我們的YourKey類別要再覆寫GetHashCode()函式。

改寫後如下:
    
class YourKey : IEquatable
{
public int ID;
#region IEquatable 成員
public bool Equals(YourKey other)
{
return this.ID == other.ID;
}
#endregion
public override int GetHashCode()
{
return ID.GetHashCode();
}
}

在覆寫的GetHashCode()中,我們就直接呼叫整數型別ID的GetHashCode()函式來作為我們的YourKey類別的HashCode函式。

到此,這樣的YourKey類別算是可以正常地在Dictionary<>中使用了。

留言

這個網誌中的熱門文章

DOS Batch指令檔中如何記錄log資訊

用捷徑方式執行需帶入命令列參數的Windows Form程式

使用regular expression來match中括號(square bracket)