Hatena::ブログ(Diary)

アジャイルプログラマの日常 このページをアンテナに追加 RSSフィード

2007-10-26

IEquatable と GetHashCode() はセットで実装

|  IEquatable と GetHashCode() はセットで実装を含むブックマーク  IEquatable と GetHashCode() はセットで実装のブックマークコメント

概要

.NET (C#, VB.NET) で、ジェネリックコレクションに独自の型を格納するときは、 IEquatable<T> や IComparable<T> と GetHashCode() の実装を行います。

GetHashCode() の実装は忘れやすいため、注意が必要です。これらを実装することで、独自の型でキーが同じかどうかを確認できるようになります。

背景

MSDN で Dictionary<T> の解説を見ると、以下のような記述があります。

Dictionary は、キーが同じであるかどうかを確認するための等値比較の実装を必要とします。comparer パラメータを受け付けるコンストラクタを使用して、IEqualityComparer ジェネリック インターフェイスの実装を指定できます。実装を指定しない場合は、既定のジェネリック等値比較演算子である EqualityComparer.Default が使用されます。型 TKey が System.IEquatable ジェネリック インターフェイスを実装している場合は、既定の等値比較演算子でその実装が使用されます。

この記述だけを見ると、作成したクラスに IEquatable<T> さえ実装すれば Dictionary<T> のキーにしても大丈夫な印象を受けますが、実際には GetHashCode() も実装する必要があります。

試しに GetHashCode() の実装を行わず IEquatable<T> だけ実装して IEquatable<T>.Equals(T other) にブレークポイントを置いても、その位置で実行が止まらないことが確認できます。

Dictionary<T> だけでなく List<T> などのジェネリックコレクション全般にいえることですので、 GetHashCode() をオーバーライドするのを忘れないように注意しましょう。

サンプル

IEquatable<T> を実装するサンプルを以下に示します。このサンプルでは、名前と誕生日を持った Person クラスを作成し、 IEquatable<T> を実装しています。同時に、 GetHashCode() をオーバーライドしています。 *1

public class Person : IEquatable<Person>
{
    private string name;
    private DateTime birthday;

    public string Name
    {
        get { return name; }
    }

    public DateTime Birthday
    {
        get { return birthday; }
    }

    public Person(string name, DateTime birthday)
    {
        this.name = name;
        this.birthday = birthday;
    }

    // 実装するのを忘れやすいので注意
    public override int GetHashCode()
    {
        return name.GetHashCode() ^ birthday.GetHashCode();
    }

    #region IEquatable<Person> メンバ

    bool IEquatable<Person>.Equals(Person other)
    {
        if (other == null)
        {
            return false;
        }

        return name == other.name && birthday == other.birthday;
    }

    #endregion
}

Dictionary<T> のキーとして作成した Person を使用するサンプルを以下に示します。この例では、 Person を住所に関連づけています。 *2

実行すると「address: Osaka」が返され、比較が正しく行われていることがわかります。

Dictionary<Person, string> addressBook = new Dictionary<Person, string>();

addressBook.Add(new Person("foo", new DateTime(2007, 3, 4)), "Tokyo");
addressBook.Add(new Person("bar", new DateTime(2007, 4, 1)), "Osaka");
addressBook.Add(new Person("bar", new DateTime(2007, 4, 2)), "Hokkaido");

string address;

if (addressBook.TryGetValue(new Person("bar", new DateTime(2007, 4, 1)), out address))
{
    System.Diagnostics.Debug.WriteLine("address: " + address);
}
else
{
    System.Diagnostics.Debug.WriteLine("address: not found");
}

// [実行結果]
// address: Osaka

*1:役に立たないクラスですが、サンプルなので……

*2:IEquatable と GetHashCode() の説明をするためだけの、意味のないサンプルです。

ISTIST 2015/01/07 16:01 勉強になりました。
調べてみたところ、IEqualityComparer<T>に Equals と GetHashCode のメソッドが用意されていますね。

ISTIST 2015/01/07 16:05 MSDNの説明のくだりに書いてありましたね。失礼しました。

Connection: close