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


1445

次のクラスを考える

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

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

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

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

        return base.GetHashCode();

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

sテーブルの行を表すEqualsため、メソッドをオーバーライドしました。オーバーライドするための好ましい方法はどれですか?FooFooGetHashCode

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


36
特にディクショナリを使用しているときの衝突のため、equalsとgethashcodeの両方を実装することが重要です。2つのオブジェクトが同じハッシュコードを返す場合、それらは連鎖して辞書に挿入されます。アイテムへのアクセス中は、等しいメソッドが使用されます。
DarthVader 2011年

回答:


1320

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

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

  • 2つのものが等しい場合(Equals(...) == true)、それらは同じ値を返す必要がありますGetHashCode()
  • GetHashCode()等しい場合、それらが同じである必要はありません。これは衝突であり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;
}

ああ-便宜のために、あなたはまた、提供することを検討可能性がある==!=オーバーライドするときの演算子EqualsGetHashCode


これを間違えたときに何が起こるかを示すデモがここにあります


49
あなたはそのような要因で増殖していますか?
LeandroLópez、

22
実際、おそらくそのうちの1つを失う可能性があります。重要なのは、衝突の数を最小限に抑えることです。つまり、オブジェクト{1,0,0}が{0,1,0}および{0,0,1}とは異なるハッシュを持つようにします(つまり、 )、
マークグラベル

13
数字を調整してわかりやすくしました(シードを追加しました)。一部のコードは異なる数値を使用します。たとえば、C#コンパイラー(匿名型の場合)は0x51ed270bのシードと-1521134295のシードを使用します。
Marc Gravell

76
@LeandroLópez:衝突の数が少なくなるので、通常、素数として係数が選択されます。
AndreiRînea2010

29
- 「!。ああ便宜のために、あなたも提供==と=演算子オーバーライド等しく、GetHashCodeメソッド検討するかもしれない」:不変ないオブジェクトのための演算子を==実装するマイクロソフトの意欲- msdn.microsoft.com/en-us/library/ ms173147.aspx- "不変でない型の演算子==をオーバーライドすることはお勧めしません。"
antiduh

137

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

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


20
@vanja。私はそれが関係していると信じています:オブジェクトをディクショナリに追加してからオブジェクトのIDを変更すると、後でフェッチするときに別のハッシュを使用して取得するため、ディクショナリから取得することはありません。
ANeves、2010

74
GetHashCode()関数に関するMicrosoftのドキュメントは、オブジェクトハッシュがその存続期間中一貫している必要があることを明示も暗示もしていません。実際、それはそうではない場合がある1つの許容ケースを具体的に説明してます。「オブジェクトのGetHashCodeメソッドは、オブジェクトのEqualsメソッドの戻り値を決定するオブジェクトの状態に変更がない限り、一貫して同じハッシュコードを返す必要があります。 」
PeterAllenWebb

37
「ハッシュコードはオブジェクトの存続期間中に変更すべきではありません」-それは真実ではありません。
黙示録

7
「オブジェクトがコレクションのキーとして使用されている間、ハッシュコード(またはイコールの評価)は変更する必要があります」と言うより良い方法です。したがって、オブジェクトをディクショナリにキーとして追加する場合は、 GetHashCodeおよびEqualsは、ディクショナリからオブジェクトを削除するまで、特定の入力の出力を変更しません。
Scott Chamberlain

11
@ScottChamberlainコメントで忘れてはいけないと思いますが、「オブジェクトがコレクションのキーとして使用されている間は、ハッシュコード(またはイコールの評価)は変更しないでください」です。正しい?
スタンプロコップ2014

57

Equalsをオーバーライドすることで、特定の型の2つのインスタンスを比較する方法をよりよく理解していることを基本的に示しているため、最高のハッシュコードを提供するための最良の候補になる可能性があります。

これは、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;
    }
}

ご覧のとおり、クラスのすべてのフィールドに基づいて適切なハッシュコードを推測しようとしていますが、オブジェクトのドメインまたは値の範囲がわかっているため、より適切なものを提供できます。


7
これは常にゼロを返しませんか?おそらく結果を1に初期化する必要があります!また、セミコロンがさらにいくつか必要です。
Sam Mackrill、2012

16
XOR演算子(^)の機能を知っていますか?
Stephen Drew

1
言ったように、これはR#が求めたときに(少なくとも2008年に行われたものです)のために書かれたものです。明らかに、このスニペットは、プログラマーが何らかの方法で調整することを目的としています。欠落しているセミコロンについては...そうです、Visual Studioの領域選択からコードをコピーして貼り付けたときに、それらを省略したように見えます。私は人々が両方を理解するだろうとも思った。
トラップ

3
@SamMackrill不足しているセミコロンに追加しました。
マシューマードック

5
@SamMackrillいいえ、それは常に0を返しません0 ^ a = a、そう0 ^ m_someVar1 = m_someVar1。の初期値をresultに設定することもできm_someVar1ます。
ミリー・スミス

41

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

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

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

この理由は、とのEquals比較時にfalseを返す必要があるためですnullhttp://msdn.microsoft.com/en-us/library/bsc2ak47.aspx参照してください。


6
このタイプのチェックは、サブクラスが独自の比較の一部としてスーパークラスのEqualsメソッドを参照している場合(つまり、base.Equals(obj))、代わりに使用する必要がある状況では失敗します
sweetfa

@sweetfa:サブクラスのEqualsメソッドの実装方法によって異なります。正常に動作するbase.Equals((BaseType)obj))を呼び出すこともできます。
huha 2013

2
いいえ、ありません:msdn.microsoft.com/en-us/library/system.object.gettype.aspx。さらに、メソッドの実装は、その呼び出し方法に応じて、失敗したり成功したりしてはなりません。オブジェクトのruntime-typeがベースクラスのサブクラスである場合、ベースクラスのEquals()がどのように呼び出されても、ベースクラスのEquals()がobj実際に等しい場合はtrueを返す必要thisがあります。
ジュピター

2
fooItem一番上に移動してからnullかどうかをチェックすると、nullまたは間違った型の場合にパフォーマンスが向上します。
IllidanS4がモニカに2017

1
@ 40Alphaええ、ええ、それではobj as Foo無効になります。
IllidanS4はモニカを

35

どうですか:

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

パフォーマンスは問題ではないと仮定:)



32
いいえ、intを返すStringオブジェクトからGetHashCode()を呼び出します。
Richard Clayton、

3
値の型に関連するボクシングだけでなく、のパフォーマンスについても、これが思ったほど高速になるとは思いませんstring.Format。私が見たもう1つのマニアックなものはnew { prop1, prop2, prop3 }.GetHashCode()です。これらの2つの間でどちらが遅いかについてはコメントできません。ツールを乱用しないでください。
nawfal 2013

16
これがためにtrueを返します{ prop1="_X", prop2="Y", prop3="Z" }{ prop1="", prop2="X_Y", prop3="Z_" }。あなたはおそらくそれを望まないでしょう。
voetsjoeba 2014年

2
うん、あなたはいつも一般的ではない何か(例えば•、▲、►、◄、☺、☻)とアンダースコア記号を置き換えることができますし、... :)ユーザーがこれらの記号を使用しないことを願っています
Ludmil Tinkov

13

対処すべき問題が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;

        if (fooItem == null)
        {
           return false;
        }

        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");
    }
}

5
GetHashCodeから例外をスローすると、オブジェクトコントラクトに違反します。GetHashCode等しい2つのオブジェクトが同じハッシュコードを返すように関数を定義するのは簡単です。return 24601;そしてreturn 8675309;両方の有効な実装になりますGetHashCode。のパフォーマンスはDictionary、アイテム数が少ない場合にのみ適切であり、アイテム数が多くなると非常に悪くなりますが、どの場合でも正しく機能します。
スーパーキャット2013

2
@supercat、オブジェクト内の識別フィールドが変更される可能性がある場合、ハッシュコードは決して変更してはならないため、適切な方法でGetHashCodeを実装することはできません。あなたが言うことを行うと、誰かがパフォーマンスの問題を追跡するのに何日も費やさなければならず、その後、辞書の使用を削除するために再設計された大規模なシステムで何週間もかかる可能性があります。
Ian Ringrose

2
私は、Equals()を必要とする定義済みのすべてのクラスに対してこのようなことをしていましたが、コレクション内のキーとしてそのオブジェクトを使用することは絶対にありませんでした。その後、ある日、DevExpress XtraGridコントロールへの入力としてそのようなオブジェクトを使用していたプログラムがクラッシュしました。XtraGridは、私の背後にある、オブジェクトに基づいてHashTableまたは何かを作成していたことがわかりました。これについてDevExpressのサポート担当者とちょっとした議論がありました。彼らがコンポーネントの機能性と信頼性を、あいまいなメソッドの未知の顧客実装に基づいているのは賢くないと私は言いました。
RenniePet 2014年

DevExpressの人々はかなりぎこちなく、基本的に、GetHashCode()メソッドで例外をスローするのは馬鹿である必要があると言っていました。私はまだ彼らがやっていることを行う別の方法を見つける必要があると思います-GetHashCode()に依存せずに任意のオブジェクトのディクショナリを構築する方法を説明する別のスレッドでMarc Gravellを思い出します-彼がそれをやった方法を思い出せませんでも。
RenniePet 14年

4
@RenniePetは、例外をスローしたためにクラッシュし、無効な実装のためにバグを見つけるのが非常に困難になります。
Ian Ringrose 2014年

12

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


11

上記の答えを追加するだけです:

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

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

さらに、ディクショナリでオブジェクトを使用した場合にオブジェクトを見つけられない可能性があるという問題を除いて(1つのハッシュコードによって挿入されたため、デフォルトのハッシュコードはおそらく異なるため、Equals() Marc Gravellが彼の答えで説明しているように、呼び出されることさえありません。また、同じキーを許可しないディクショナリまたはハッシュセットの概念の違反も導入します。これらのオブジェクトは、Equalsをオーバーライドしたときに本質的に同じであるとすでに宣言しているため、一意のキーを持っていると思われるデータ構造上の異なるキーとして両方を必要とすることはありませんが、ハッシュコードが異なるため、「同じ」キーが異なるキーとして挿入されます。


8

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


7

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

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

(もちろん、#pragmaを使用して警告をオフにすることもできますが、この方法を好みます。)

もちろん、パフォーマンスを必要とする立場にいるとき、ここで他の人が述べたすべての問題が当てはまります。最も重要 -それ以外の場合、ハッシュセットまたはディクショナリからアイテムを取得するときに誤った結果が発生します。ハッシュコードは、オブジェクトのライフタイムによって変化してはなりません(より正確には、ハッシュコードが必要なときなど、ディクショナリのキー):たとえば、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
      }
   }

3
反対投票。これは明らかに間違っています。Microsoftでも、MSDN(msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx)で、呼び出しの戻り値に影響を与える可能性のある方法でオブジェクトの状態を変更すると、GetHashCodeの値を変更する必要があると述べていますEquals()に加えて、その例でも、パブリックに変更可能な値に完全に依存するGetHashCode実装も示しています。
Sebastian PR Gingter 2013年

セバスチャン、同意しない:ハッシュコードを使用するコレクションにオブジェクトを追加すると、そのオブジェクトはハッシュコードに応じてビンに入れられます。ハッシュコードを変更すると、間違ったビンが検索されるため、コレクション内でオブジェクトが再び見つかることはありません。これは実際、コードで発生したことであり、それを指摘する必要があることがわかりました。
ILoveFortran 2013年

2
セバスチャン、さらに、GetHashCode()を変更する必要があるというリンク(msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx)のステートメントを確認できません。反対に、Equalsが同じ引数に対して同じ値を返す限り、変更しないでください。「オブジェクトのGetHashCodeメソッドは、戻り値を決定するオブジェクトの状態に変更がない限り、一貫して同じハッシュコードを返す必要があります。オブジェクトのEqualsメソッドの「このステートメントは、Equalsの戻り値が変更された場合に変更する必要があることを意味するものではありません。
ILoveFortran 2013年

2
@ジョアン、あなたはプロデューサー/インプリメンターとの契約のクライアント/コンシューマー側を混乱させています。GetHashCode()をオーバーライドする実装者の責任について話しています。あなたは消費者、つまり価値を使用している消費者について話しています。
ILoveFortran 2013

1
完全な誤解... :)真実は、状態がオブジェクトのIDと無関係でない限り、オブジェクトの状態が変化するとハッシュコードも変化する必要があるということです。また、MUTABLEオブジェクトをコレクションのキーとして使用しないでください。この目的には、読み取り専用オブジェクトを使用してください。GetHashCode、Equals ...など、現時点で名前を覚えていない他のいくつかのメソッドはスローしないでください。
darlove

0

Equals()で定義されているように2つのオブジェクトが等しい場合、それらが同じハッシュコードを返すことを常に保証する必要があります。他のコメントのいくつかが述べているように、オブジェクトがHashSetやディクショナリのようなハッシュベースのコンテナーで使用されない場合、理論的にはこれは必須ではありません。ただし、常にこのルールに従うことをお勧めします。その理由は、実際にパフォーマンスを改善したり、コードのセマンティクスをより適切に伝達したりするために、コレクションをあるタイプから別のタイプに変更するのは簡単すぎるためです。

たとえば、いくつかのオブジェクトをリストに保持するとします。後で誰かが実際に、たとえばHashSetの方が検索特性が優れているため、はるかに優れた選択肢であることを実感しました。これは私たちがトラブルに巻き込まれるときです。Listは、内部的に型のデフォルトの等値比較子を使用します。これは、HashSetがGetHashCode()を使用する場合にEqualsを意味します。2つが異なる動作をする場合、プログラムも同様です。また、そのような問題はトラブルシューティングが最も簡単ではないことを覚えておいてください。

この動作を、他のいくつかのGetHashCode()の落とし穴とまとめて、ブログの投稿で例や説明を見つけることができます。


0

.NET 4.7オーバーライドの好ましい方法として、GetHashCode()以下に示します。古い.NETバージョンを対象とする場合は、System.ValueTuple nugetパッケージを含めます。

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

パフォーマンスの点では、この方法はほとんどの複合ハッシュコード実装よりも優れています。ValueTupleがあるstructので、任意のゴミがないだろう、と根本的なアルゴリズムは、早くそれを取得としてあります。


-1

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

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


-6

以下では、リフレクションを使用する方が、プロパティの追加/削除について心配する必要がないので、パブリックプロパティを考慮したより良いオプションのように思えます(それほど一般的なシナリオではありません)。これもパフォーマンスが優れていることがわかりました(ダイアゴニスティックスストップウォッチを使用した場合との比較)。

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }

12
GetHashCode()の実装は非常に軽量であることが期待されています。リフレクションの使用が何千もの呼び出しのStopWatchで顕著であるかどうかはわかりませんが、数百万の呼び出しに確実に当てはまります(リストから辞書を作成することを考えてください)。
bohdan_trotsenko 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.