もこたんブログ@mocuLab(・ω・)

Programming & Designing

C#で自作クラス(カスタムクラス)をキーにする方法(Dictionary,HashSet)

ディクショナリの内部では、値(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; set; }
    /// <summary>電話</summary>
    public string Tel { get; set; }

    public UserKey(string naem, string tel)
    {
        this.Name = naem;
        this.Tel = tel;
    }

    // 
    // overrideしておきます(´・ω・`)
    // 
    public override int GetHashCode()
    {
        return Name.GetHashCode() * 31 + 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.Tel == other.Tel;
    }
}

自作クラスをディクショナリのキーに使った例

上記のUserKeyクラスをディクショナリ型のキーに使ってみた例です。

var userKeyList = new List<UserKey>()
{
    new UserKey("名前1", "00-0000"),
    new UserKey("名前2", "00-0000"),
    new UserKey("名前1", "00-0000"),// 同じキー
    new UserKey("名前1", "11-1111"),
};

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", "00-0000");
var key2 = new UserKey("名前1", "00-0000");
var key3 = new UserKey("名前2", "00-0000");

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", "00-0000");
var uKey2 = new UserKey("名前1", "00-0000");

// 等しいというのを調べるのも、オーバーライドしたEqualsメソッドを使えばOK
bool isEqual1 = (uKey1 == uKey2);// false
bool isEqual2 = uKey1.Equals(uKey2);// true