ディクショナリの内部では、値(value)を特定するためのインデックスとしてハッシュコード(ハッシュ値)が使われています。
(1)まずGetHashCodeメソッドで、ハッシュ値が同じかを調べる
(2)ハッシュ値が同じときは、Equalsメソッドでオブジェクトの同値性を調べる
1と2が同じだったら、同じと判断する。
GetHashCodeメソッド、Equalsメソッドを自分でオーバーライドしないときは、Object型のEqualsメソッドが呼ばれます。
Object型のEqualsメソッドは参照アドレスの値を比較するみたいです。それぞれnewしたオブジェクト同士は、プロパティの値が同じでも参照アドレスが一致しないので、別物と判断されます。
◼︎ハッシュコード(ハッシュ値)
→オブジェクトの値を元に何らかの計算を行なって求めたint型の値。
ディクショナリのキーに使うクラスの定義
例として、氏名と電話の2つをキーとして使い、2が同じ時に同じであると判断してほしいとします。
キーに使うクラスで
・GetHashCodeメソッドをオーバーライドする
・Equalsメソッドをオーバーライドする
をしておきます。
public class UserKey { /// <summary>氏名</summary> public string Name { get; private set; } /// <summary>生年月日</summary> public string Birthday { get; private set; } /// <summary>電話</summary> public string Tel { get; private set; } public UserKey(string naem, string birthday, string tel) { this.Name = naem; this.Birthday = birthday; this.Tel = tel; } // // overrideしておきます(´・ω・`) // public override int GetHashCode() { // Javaでは31を乗算するのが一般的らしい //return Name.GetHashCode() * 31 // + Birthday.GetHashCode() * 31 // + Tel.GetHashCode() * 31; // C#では^演算子でXOR(排他的論理和)演算するのが普通 return Name.GetHashCode() ^ Birthday.GetHashCode() ^ Tel.GetHashCode(); } public override bool Equals(object obj) { // asでは、objがnullでも例外は発生せずにnullが入ってくる var other = obj as UserKey; if (other == null) return false; // 何が同じときに、「同じ」と判断してほしいかを記述する return this.Name == other.Name && this.Birthday == other.Birthday && this.Tel == other.Tel; } }
自作クラスをディクショナリのキーに使った例
上記のUserKeyクラスをディクショナリ型のキーに使ってみた例です。
var userKeyList = new List<UserKey>() { new UserKey("名前1", "20180101", "00-0000"),// 1 new UserKey("名前2", "20180101", "00-0000"),// 2 new UserKey("名前1", "20180101", "00-0000"),// 3(1と同じキー) new UserKey("名前1", "20180101", "11-1111"),// 4 }; var userDic = new Dictionary<UserKey, List<string>>(); foreach (var userKey in userKeyList) { if (userDic.ContainsKey(userKey)) { // GetHashCodeメソッドで一致したとき // Equalsメソッドでも一致するかを確認する。 // なので両方オーバーライドしないといけない。 continue; } userDic.Add(userKey, new List<string>()); }
HashSetでの使用例
newしているので参照アドレスが違いますが、Addメソッドで同じと判断されてます。
var hashset = new HashSet<UserKey>(); var key1 = new UserKey("名前1", "20180101", "00-0000");// 1 var key2 = new UserKey("名前1", "20180101", "00-0000");// 2(1と同じキー) var key3 = new UserKey("名前2", "20180101", "00-0000");// 3 bool isAdded1 = hashset.Add(key1);// true bool isAdded2 = hashset.Add(key2);// false bool isAdded3 = hashset.Add(key3);// true
「演算子 ==」の場合も同様
「演算子 ==」は、既定では参照の等価(オブジェクトへの参照アドレスが同じかどうか)を調べます。それぞれnewしたオブジェクト同士だと、参照アドレスが違うため元々の動作ではfalseが返ります。
Equals() と演算子 == のオーバーロードに関するガイドライン (C# プログラミング ガイド)
https://msdn.microsoft.com/ja-jp/library/ms173147(v=vs.90).aspx
var uKey1 = new UserKey("名前1", "20180101", "00-0000");// 1 var uKey2 = new UserKey("名前1", "20180101", "00-0000");// 2(1と同じキー) // 等しいというのを調べるのも、オーバーライドしたEqualsメソッドを使えばOK bool isEqual1 = (uKey1 == uKey2);// false bool isEqual2 = uKey1.Equals(uKey2);// true