web-dev-qa-db-ja.com

EqualsメソッドがオーバーライドされたときにGetHashCodeをオーバーライドすることが重要なのはなぜですか?

次のクラスを考える

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

EqualsFoosテーブルの行を表すため、Fooメソッドをオーバーライドしました。 GetHashCodeをオーバーライドするのに適した方法はどれですか?

なぜGetHashCodeをオーバーライドすることが重要なのですか?

1303
David Basarab

はい、アイテムを辞書のキーとして使用するか、HashSet<T>などを使用することが重要です。これは、アイテムをバケットにグループ化するために(カスタムIEqualityComparer<T>がない場合)使用されるためです。 2つのアイテムのハッシュコードが一致しない場合、それらはneverが等しいと見なされる可能性があります(Equalsは決して呼び出されません)。

GetHashCode()メソッドはEqualsロジックを反映する必要があります。ルールは次のとおりです。

  • 2つのものが等しい(Equals(...) == true)場合、それらはmustGetHashCode()に同じ値を返します
  • GetHashCode()が等しい場合、それらが同じであるためにはnotが必要です。これは衝突であり、Equalsが呼び出されて、それが真に等しいかどうかを確認します。

この場合、「return FooId;」が適切なGetHashCode()実装であるように見えます。複数のプロパティをテストしている場合、以下のようなコードを使用してそれらを組み合わせて、斜め衝突を減らすのが一般的です(つまり、new Foo(3,5)new Foo(5,3)と異なるハッシュコードを持つように):

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

ああ-便宜上、EqualsおよびGetHashCodeをオーバーライドするときに、==および!=演算子を提供することも検討できます。


これを間違えたときに何が起こるかを示すデモは here です。

1238
Marc Gravell

Marcがすでに述べた規則に加えて、ハッシュコードはオブジェクトの存続期間中に変更してはいけないため、GetHashCode()を正しく実装するのは実際には非常に困難です。したがって、ハッシュコードを計算するために使用されるフィールドは不変でなければなりません。

NHibernateで作業していたとき、私はついにこの問題の解決策を見つけました。私のアプローチは、オブジェクトのIDからハッシュコードを計算することです。 IDはコンストラクタからしか設定できないため、IDを変更したい場合は非常にまれですが、新しいID、したがって新しいハッシュコードを持つ新しいオブジェクトを作成する必要があります。ランダムにIDを生成するパラメータのないコンストラクタを提供できるため、このアプローチはGUIDで最もうまく機能します。

125
Albic

オーバーライドすることであなたは基本的に、あなたは最高のハッシュコードを提供するための最良の候補である可能性が高いですので、与えられたタイプの2つのインスタンスを比較する方法をよりよく知っている1であることを述べているに等しくなります。

これは、ReSharperのは、あなたのためのGetHashCodeメソッド()関数を書き込む方法の例です。

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

あなたが見ることができるようにそれはちょうど、クラス内のすべてのフィールドに基づいて、良いハッシュコードを推測しようとしますが、あなたが知っているので、あなたのオブジェクトのドメインまたは値は、あなたがまだ良いものを提供できる範囲です。

50
Trap

Equals()をオーバーライドするときは、objパラメータをnullに対してチェックすることを忘れないでください。そしてまたタイプを比較しなさい。

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

その理由は次のとおりです。Equalsは、nullと比較してfalseを返す必要があります。 http://msdn.Microsoft.com/ja-jp/library/bsc2ak47.aspx も参照してください。

36
huha

どうですか?

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

パフォーマンスが問題にならないと仮定すると:)

27
Ludmil Tinkov

上記の回答を追加するだけです。

Equalsをオーバーライドしない場合、デフォルトの動作はオブジェクトの参照が比較されることです。同じことがハッシュコードにも当てはまります - デフォルトの実装は、通常、参照のメモリアドレスに基づいています。 Equalsをオーバーライドしたので、正しい動作は、参照ではなくEqualsに実装したものをすべて比較することであることを意味します。したがって、ハッシュコードについても同様にしてください。

あなたのクラスのクライアントはハッシュコードがequalsメソッドに似たロジックを持つことを期待するでしょう。例えばIEqualityComparerを使うlinqメソッドは最初にハッシュコードを比較し、等しい場合にのみEquals()メソッドを比較します。実行するために、ハッシュコードを実装しなかった場合、等しいオブジェクトはおそらく異なるハッシュコードを持ち(異なるメモリアドレスを持つため)、等しくないと誤って判断されます(Equals()はヒットしません)。

さらに、それを1つのハッシュコードで挿入したため、それを探すときにデフォルトのハッシュコードが異なる場合があるため、オブジェクトを辞書で使用した場合にオブジェクトが見つからない可能性があります。 Marc Gravellが彼の答えで説明しているように、呼ばれることすらありません、あなたは同じキーを許すべきではない辞書またはハッシュセットの概念の違反も紹介します - 両方とも一意のキーを持つことを前提としたデータ構造上の異なるキーとしては使用できませんが、ハッシュコードが異なるため、「同じ」キーが別のキーとして挿入されます。

9
BornToCode

フレームワークでは、同じ2つのオブジェクトが同じハッシュコードを持つ必要があるためです。 2つのオブジェクトの特別な比較を行うためにequalsメソッドをオーバーライドし、2つのオブジェクトがメソッドによって同じであると見なされる場合、2つのオブジェクトのハッシュコードも同じである必要があります。 (辞書とハッシュテーブルはこの原則に依存します)。

9
kemiller2002

対処しなければならない問題が2つあります。

  1. オブジェクト内のフィールドを変更できる場合は、適切なGetHashCode()を指定することはできません。また、多くの場合、オブジェクトはGetHashCode()に依存するコレクションでは決して使用されません。そのため、GetHashCode()を実装するためのコストは、それだけの価値がないか、不可能です。

  2. 誰かがGetHashCode()を呼び出すコレクションにあなたのオブジェクトを置き、あなたがEquals()を正しい方法で振る舞わせずにGetHashCode()をオーバーライドした場合、その人は問題を追跡するのに何日も費やすかもしれません。

したがって、デフォルトで私は行います。

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}
9
Ian Ringrose

ハッシュコードは、Dictionary、Hashtable、HashSetなどのハッシュベースのコレクションに使用されます。このコードの目的は、特定のオブジェクトを特定のグループ(バケット)に入れることによって、すばやく事前に並べ替えることです。コードが含まれているすべてのオブジェクトではなく1つのバケット内でオブジェクトを検索する必要があるため、この事前ソートはハッシュコレクションから取得する必要があるときにこのオブジェクトを見つけるのに非常に役立ちます。ハッシュコードのより良い分布(より良い一意性)はより速い検索です。各オブジェクトが一意のハッシュコードを持つ理想的な状況では、それを見つけることはO(1)操作です。ほとんどの場合、それはO(1)に近づきます。

6
Maciej

それは必ずしも重要ではありません。それはあなたのコレクションのサイズとあなたのパフォーマンス要件、そしてあなたがあなたのクラスがあなたがパフォーマンス要件を知らないかもしれないライブラリで使われるかどうかに依存します。私は私のコレクションのサイズがそれほど大きくないことをよく知っています、そして私の時間は完璧なハッシュコードを作成することによって得られる数マイクロ秒のパフォーマンスより貴重です。だから(コンパイラによる迷惑な警告を取り除くために)私は単に使用します:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(もちろん、#pragmaを使って警告を消すこともできますが、私はこの方法を好みます。)

あなたがあなたの立場にいるとき、あなたがdo他の人によって言及された問題の全てがあてはまるよりもパフォーマンスを必要とする、もちろん。 最も重要 - そうでなければ、ハッシュセットや辞書からアイテムを検索するときに間違った結果を得るでしょう:ハッシュコードはオブジェクトの存続期間によって変化してはいけません(より正確にはハッシュコードが必要なときはいつでも(辞書のキーになっている間など):たとえば、Valueはpublicなので次のように間違っているため、インスタンスの有効期間中はクラスの外部で変更できます。ハッシュコードの基礎として使用しないでください。


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

一方、Valueを変更できない場合は、使用してもかまいません。


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }

4
ILoveFortran

元のGetHashCode()がオブジェクトのメモリアドレスを返すことは私の理解です。したがって、2つの異なるオブジェクトを比較したい場合は、それをオーバーライドすることが不可欠です。

編集:それは間違っていた、元のGetHashCode()メソッドは2つの値の同等性を保証することはできません。等しいオブジェクトは同じハッシュコードを返しますが。

0
user2855602