C#のGetHashCodeガイドライン


136

Essential C#3.0と.NET 3.5の本で次のことを読みました。

オブジェクトのデータが変更された場合でも、特定のオブジェクトの存続期間中のGetHashCode()の戻り値は一定(同じ値)である必要があります。多くの場合、これを実施するには、メソッドの戻り値をキャッシュする必要があります。

これは有効なガイドラインですか?

.NETでいくつかの組み込み型を試してみましたが、これらはこのように動作しませんでした。


可能であれば、受け入れられた回答を変更することを検討してください。
Giffyguy 2015

回答:


93

答えはほとんどが、有効なガイドラインですが、おそらく有効なルールではありません。それはまた、全体の話をしません。

重要な点は、2つの等しいオブジェクトは同じハッシュコードを返す必要があり、ハッシュコードはオブジェクトの存続期間中有効でなければならないため、可変型の場合、ハッシュコードを可変データに基づくことはできないということです。ハッシュコードが変更されると、オブジェクトは正しいハッシュビンに存在しなくなるため、ハッシュされたコレクションで失われることになります。

たとえば、オブジェクトAは1のハッシュを返すため、ハッシュテーブルのビン1に入ります。次に、オブジェクトAを変更して、ハッシュ2を返すようにします。ハッシュテーブルがハッシュを探しに行くと、オブジェクトはビン2にあり、それを見つけることができません。オブジェクトはビン1に孤立しています。これが、ハッシュコードが必要な理由です。オブジェクトの存続期間中は変更ないでください。GetHashCode実装を作成するのがお尻の苦痛である理由の1つにすぎません。

更新
Eric Lippertが、に関する優れた情報を提供するブログ投稿しましたGetHashCode

追加の更新
上記の変更をいくつか行いました。

  1. ガイドラインとルールを区別しました。
  2. 「オブジェクトの存続期間中」を徹底的に見直しました。

ガイドラインは単なるガイドであり、ルールではありません。実際にGetHashCodeは、オブジェクトがハッシュテーブルに格納されている場合など、オブジェクトがガイドラインに従うことが期待される場合にのみ、これらのガイドラインに従う必要があります。ハッシュテーブル(またはの規則に依存するその他のもの)でオブジェクトを使用する予定GetHashCodeがない場合、実装はガイドラインに従う必要はありません。

「オブジェクトの存続期間中」が表示された場合は、「オブジェクトがハッシュテーブルと連携する必要がある時間」などを読んでください。ほとんどのGetHashCode場合と同様に、ルールを破るタイミングを知ることです。


1
変更可能な型間の等価性をどのように判断しますか?
Jon B、

9
等しいかどうかを判断するためにGetHashCodeを使用しないでください。
JSBձոգչ2009年

4
@JS Bangs-MSDNから:GetHashCodeをオーバーライドする派生クラスは、Equalsもオーバーライドして、等しいと見なされる2つのオブジェクトが同じハッシュコードを持つことを保証する必要があります。そうしないと、Hashtable型が正しく機能しない可能性があります。
Jon B、

3
@Joan Venge:2つのこと。まず、Microsoftでさえ、すべての実装でGetHashCodeを正しく持っているわけではありません。第2に、値の型は一般に不変であり、すべての値は既存のインスタンスの変更ではなく新しいインスタンスです。
ジェフイエーツ

17
a.Equals(b)はa.GetHashCode()== b.GetHashCode()を意味する必要があるため、等価比較に使用されるデータが変更された場合、ハッシュコードを変更する必要があります。問題は、可変データに基づくGetHashCodeではないと思います。問題は、可変オブジェクトをハッシュテーブルキーとして使用することです(実際にはそれらを変更します)。私が間違っている?
Niklas 2010

120

久しぶりですが、なぜか、どういうことかなど、この質問には正しい答えが必要だと思います。これまでの最良の答えは、MSDNを徹底的に引用しているものです。独自のルールを作成しようとしないでください。MSの担当者は彼らが何をしているかを知っていました。

しかし、まず最初に、質問で引用されているガイドラインは間違っています。

さて、なぜですか-2つあります

最初の理由:ハッシュコードが計算された場合、オブジェクト自体が変更されても、オブジェクトのライフタイム中にハッシュコードが変更されないため、等号契約が破られます。

「2つのオブジェクトが等しいと比較する場合、各オブジェクトのGetHashCodeメソッドは同じ値を返す必要があります。ただし、2つのオブジェクトが等しいと比較しない場合、2つのオブジェクトのGetHashCodeメソッドは異なる値を返す必要はありません。」

2番目の文は、「唯一のルールは、オブジェクトの作成時に、等しいオブジェクトのハッシュコードが等しくなければならないことです」と誤って解釈されることがよくあります。理由は本当にわかりませんが、ここでもほとんどの答えの本質についてです。

名前がequalsメソッドで使用されている名前を含む2つのオブジェクトについて考えてみましょう。同じ名前->同じもの。インスタンスAの作成:名前= JoeインスタンスBの作成:名前= Peter

ハッシュコードAとハッシュコードBはおそらく同じではありません。インスタンスBの名前がJoeに変更されると、どうなりますか?

質問のガイドラインによると、Bのハッシュコードは変更されません。この結果は次のようになります:A.Equals(B)==> trueしかし、同時に:A.GetHashCode()== B.GetHashCode()==> false。

しかし、まさにこの動作は、equals&hashcode-contractによって明示的に禁止されています。

2番目の理由:もちろんですが、ハッシュコードを変更すると、ハッシュコードを使用してハッシュされたリストやその他のオブジェクトが破壊される可能性がありますが、その逆も同様です。ハッシュコードを変更しないと、最悪の場合、ハッシュされたリストが取得されます。多くの異なるオブジェクトはすべて同じハッシュコードを持ち、そのため同じハッシュビンにあります。たとえば、オブジェクトが標準値で初期化されている場合に発生します。


さて、一見すると、矛盾があるようです-どちらにしても、コードは壊れます。しかし、どちらの問題も、変更された、または変更されていないハッシュコードからは発生しません。

問題の原因は、MSDNで詳しく説明されています。

MSDNのハッシュテーブルエントリから:

キーオブジェクトは、Hashtableでキーとして使用されている限り、不変である必要があります。

これは意味します:

ハッシュ値を作成するオブジェクトは、オブジェクトが変更されたときにハッシュ値を変更する必要がありますが、Hashtable(または他のHashを使用するオブジェクト)内で使用する場合、オブジェクト自体への変更を許可してはいけません。 。

最初に、もちろん最も簡単な方法は、ハッシュテーブルで使用するためにのみ不変オブジェクトを設計することです。これは、必要に応じて通常の可変オブジェクトのコピーとして作成されます。不変オブジェクトの内部では、ハッシュコードは不変であるため、キャッシュしても問題ありません。

次に、オブジェクトに「ハッシュされています」フラグを設定します。すべてのオブジェクトデータがプライベートであることを確認し、オブジェクトデータを変更できるすべての関数のフラグを確認し、変更が許可されていない場合(つまり、フラグが設定されている場合)例外データをスローします。 )。ここで、ハッシュされた領域にオブジェクトを配置するときは、フラグを設定してください。フラグが不要になったら、フラグを解除してください。使いやすくするために、「GetHashCode」メソッド内でフラグを自動的に設定することをお勧めします。これにより、忘れることができなくなります。そして、「ResetHashFlag」メソッドの明示的な呼び出しは、プログラマがオブジェクトデータを変更することが許可されているかどうかを検討する必要があることを確認します。

同様に、何を言っておくべきか:オブジェクトデータが変更されたときに、equals&hashcode-contractに違反することなく、ハッシュコードが変更されていない、可変データを持つオブジェクトが存在する可能性がある場合があります。

ただし、これには、equals-methodが変更可能なデータに基づいていないことも必要です。したがって、オブジェクトを記述して、値を1回だけ計算し、それをオブジェクト内に格納して後の呼び出しで返すGetHashCodeメソッドを作成する場合は、再び、次のように使用するEqualsメソッドを作成する必要があります。比較のために格納された値。A.Equals(B)がfalseからtrueに変わることはありません。そうでなければ、契約は破られるでしょう。この結果、通常、Equalsメソッドは意味をなさなくなります。これは、元の参照が等しいわけではありませんが、値が等しいこともありません。これは意図された動作(つまり、顧客の記録)である場合がありますが、通常はそうではありません。

したがって、オブジェクトデータが変更されたときにGetHashCodeの結果を変更し、リストまたはオブジェクトを使用したハッシュ内でのオブジェクトの使用が意図されている(または可能である)場合は、オブジェクトを不変にするか、読み取り専用フラグを作成して、オブジェクトを含むハッシュリストの有効期間。

(ちなみに、これはすべてC#または.NET固有ではありません-オブジェクトがリスト内にある間は、オブジェクトの識別データが変更されないのは、すべてのハッシュテーブル実装、またはより一般的にはインデックス付きリストの性質によるものです。 。このルールに違反すると、予期しない動作や予期しない動作が発生します。リスト内のすべての要素を監視し、リストの自動再インデックスを実行するリスト実装がどこかにある可能性があります。


23
この詳細な説明の+1(できればさらに説明します)
Oliver

5
+1詳細な説明のため、これは間違いなく良い答えです!:)
Joe

9

MSDNから

2つのオブジェクトが等しいと比較する場合、各オブジェクトのGetHashCodeメソッドは同じ値を返す必要があります。ただし、2つのオブジェクトが等しいと比較されない場合、2つのオブジェクトのGetHashCodeメソッドは異なる値を返す必要はありません。

オブジェクトのGetHashCodeメソッドは、オブジェクトのEqualsメソッドの戻り値を決定するオブジェクトの状態に変更がない限り、常に同じハッシュコードを返す必要があります。これはアプリケーションの現在の実行にのみ当てはまり、アプリケーションが再度実行されると別のハッシュコードが返される可能性があることに注意してください。

最高のパフォーマンスを得るには、ハッシュ関数がすべての入力に対してランダムな分布を生成する必要があります。

つまり、オブジェクトの値が変更されると、ハッシュコードも変更されます。たとえば、「Name」プロパティが「Tom」に設定された「Person」クラスには、1つのハッシュコードと、名前を「Jerry」に変更した場合は別のコードが必要です。そうでなければ、トム==ジェリー、おそらくあなたが意図したものではないでしょう。


編集

また、MSDNから:

GetHashCodeをオーバーライドする派生クラスは、Equalsもオーバーライドして、等しいと見なされる2つのオブジェクトが同じハッシュコードを持つことを保証する必要があります。そうしないと、Hashtableタイプが正しく機能しない可能性があります。

以下からのMSDNのハッシュテーブルエントリ

キーオブジェクトは、Hashtableでキーとして使用されている限り、不変である必要があります。

私がこれを読む方法は、それらがハッシュテーブルで使用するように設計されていない限り、可変オブジェクトそれらの値が変化すると異なるハッシュコードを返さなければならないということです。

System.Drawing.Pointの例では、オブジェクトは変更可能であり、かつない場合XまたはY値の変更異なるハッシュコードを返します。これは、ハッシュテーブルでそのまま使用するのに適さない候補になります。


GetHashCode()は、この関数の唯一のポイントであるハッシュテーブルで使用するように設計されています。
skolima 2010年

@skolima-MSDNドキュメントはそれと一致していません。可変オブジェクトはGetHashCode()を実装する場合があり、オブジェクトの値が変化すると、異なる値を返す必要があります。ハッシュテーブルは不変のキーを使用する必要があります。したがって、GetHashCode()をハッシュテーブル以外のものに使用できます。
Jon B

9

GetHashcodeに関するドキュメントは少しわかりにくいと思います。

一方では、MSDNは、オブジェクトのハッシュコードは決して変更されず、一定であることを述べています。他方、MSDNは、GetHashcodeの戻り値は、2つのオブジェクトが等しいと見なされた場合、それらも等しいはずであるとも述べています。

MSDN:

ハッシュ関数には次のプロパティが必要です。

  • 2つのオブジェクトが等しいと比較する場合、各オブジェクトのGetHashCodeメソッドは同じ値を返す必要があります。ただし、2つのオブジェクトが等しいと比較されない場合、2つのオブジェクトのGetHashCodeメソッドは異なる値を返す必要はありません。
  • オブジェクトのGetHashCodeメソッドは、オブジェクトのEqualsメソッドの戻り値を決定するオブジェクトの状態に変更がない限り、常に同じハッシュコードを返す必要があります。これはアプリケーションの現在の実行にのみ当てはまり、アプリケーションが再度実行されると別のハッシュコードが返される可能性があることに注意してください。
  • 最高のパフォーマンスを得るには、ハッシュ関数がすべての入力に対してランダムな分布を生成する必要があります。

次に、これは、すべてのオブジェクトが不変であるか、GetHashcodeメソッドが不変であるオブジェクトのプロパティに基づく必要があることを意味します。たとえば、次のクラス(単純な実装)があるとします。

public class SomeThing
{
      public string Name {get; set;}

      public override GetHashCode()
      {
          return Name.GetHashcode();
      }

      public override Equals(object other)
      {
           SomeThing = other as Something;
           if( other == null ) return false;
           return this.Name == other.Name;
      }
}

この実装は、MSDNにあるルールに既に違反しています。このクラスの2つのインスタンスがあるとします。instance1のNameプロパティは 'Pol'に設定され、instance2のNameプロパティは 'Piet'に設定されます。どちらのインスタンスも異なるハッシュコードを返し、それらも等しくありません。ここで、インスタンス2の名前を「Pol」に変更するとします。次に、Equalsメソッドに従って、両方のインスタンスが等しくなり、MSDNのルールの1つに従って、同じハッシュコードが返されるはずです。
ただし、instance2のハッシュコードが変更されるため、これを行うことはできません。MSDNでは、これは許可されていません。

次に、エンティティがある場合は、そのエンティティの「プライマリ識別子」を使用するようにハッシュコードを実装できます。これは、代理キーまたは不変のプロパティであることが理想的です。値オブジェクトがある場合は、その値オブジェクトの「プロパティ」を使用するようにハッシュコードを実装できます。これらのプロパティは、値オブジェクトの「定義」を構成します。これはもちろん値オブジェクトの性質です。あなたはそれのアイデンティティには興味がなく、むしろその価値に興味があります。
したがって、値オブジェクトは不変である必要があります。(それらが.NETフレームワークにあるように、文字列、日付などはすべて不変オブジェクトです)。

もう1つ頭に浮かぶのは、
どの「セッション」(どのようにこれを呼び出せばよいかわからない)の間に「GetHashCode」が定数値を返す必要があることです。アプリケーションを開き、オブジェクトのインスタンスをDB(エンティティ)からロードして、そのハッシュコードを取得するとします。特定の数を返します。アプリケーションを閉じ、同じエンティティをロードします。今回のハッシュコードは、エンティティを最初にロードしたときと同じ値である必要がありますか?私見ではありません。


1
あなたの例は、変更可能なデータに基づいてハッシュコードを作成できないとJeff Yatesが言う理由です。ハッシュコードがそのオブジェクトの変更可能な値に基づいている場合、変更可能なオブジェクトをディクショナリに貼り付けることはできません。
Ogre Psalm33 2009

3
MSDNルールに違反している場所がわかりませんか?ルールによると、オブジェクトのGetHashCodeメソッドは、オブジェクトのEqualsメソッドの戻り値を決定するオブジェクトの状態に変更ない限り、常に同じハッシュコードを返す必要があります。つまり、instance2の名前をPolに変更すると、instance2のハッシュコードを変更できます
chikak

8

これは良いアドバイスです。ブライアンペピンはこの件について次のように述べています。

これにより、私は2回以上失敗しました。GetHashCodeがインスタンスの存続期間を通じて常に同じ値を返すことを確認してください。ハッシュコードは、ほとんどのハッシュテーブル実装で「バケット」を識別するために使用されることに注意してください。オブジェクトの「バケット」が変更されると、ハッシュテーブルがオブジェクトを見つけられない場合があります。これらは見つけるのが非常に難しいバグになる可能性があるので、最初は正しく修正してください。


私は反対票を投じませんでしたが、問題全体を網羅していない引用であるため、他の人が反対票を投じたと思います。ふり文字列は変更可能でしたが、ハッシュコードは変更されませんでした。「bob」を作成し、それをハッシュテーブルのキーとして使用してから、その値を「phil」に変更します。次に、新しい文字列「phil」を作成します。次に、「フィル」というキーを持つハッシュテーブルエントリを探すと、最初に入力したアイテムは見つかりません。誰かが「ボブ」で検索した場合、それは見つかりますが、正しくない可能性のある値を取得します。変更可能なキーを使用しないように注意するか、危険に注意してください。
Eric Tuttleman、2010

@EricTuttleman:フレームワークのルールを記述していれば、オブジェクトのペアに対してX、またはY一度X.Equals(Y)またはY.Equals(X)呼び出された後のすべての呼び出しで同じ結果が得られるように指定しました。他の同等性の定義を使用したい場合は、を使用しEqualityComparer<T>ます。
スーパーキャット2013年

5

質問に直接答えることはできませんが、Resharperを使用する場合は、適切なGetHashCode実装(およびEqualsメソッド)を生成する機能があることを忘れないでください。もちろん、ハッシュコードを計算するときに考慮されるクラスのメンバーを指定できます。


ありがとう、Resharperを実際に使用したことはありませんが、頻繁に言及されているので、試してみる必要があります。
Joan

+1の場合は、Resharperが適切なGetHashCode実装を生成します。
ΩmegaMan

5

マーク・ブルックスのこのブログ投稿をチェックしてください:

VTO、RTO、GetHashCode()-ああ、私の!

その後、フォローアップの投稿(私は新しいのでリンクできませんが、最初の記事にリンクがあります)をチェックしてください。この記事では、初期の実装におけるいくつかのマイナーな弱点についてさらに詳しく説明しています。

これは、GetHashCode()実装の作成について知っておく必要があるすべてのことでした。彼は、他のユーティリティと一緒に自分のメソッドのダウンロードも提供しています。


4

ハッシュコードは変更されませんが、ハッシュコードがどこから来ているのかを理解することも重要です。

オブジェクトが値のセマンティクスを使用している場合、つまりオブジェクトのIDはその値(文字列、色、すべての構造体など)によって定義されます。オブジェクトのIDがそのすべての値から独立している場合、ハッシュコードはその値のサブセットによって識別されます。たとえば、StackOverflowエントリはデータベースのどこかに保存されています。名前または電子メールを変更しても、一部の値は変更されていますが、顧客のエントリは同じままです(最終的には、通常、長い顧客ID#によって識別されます)。

要するに:

値タイプのセマンティクス-ハッシュコードは値によって定義されます参照タイプのセマンティクス-ハッシュコードはいくつかのIDによって定義されます

これがまだ意味をなさない場合は、Eric EvansによるDomain Driven Designを読んで、エンティティと値の型(多かれ少なかれ、私が上で試みたものです)を読むことをお勧めします。


これは実際には正しくありません。ハッシュコードは、特定のインスタンスに対して一定でなければなりません。値タイプの場合、各値が一意のインスタンスである場合が多いため、ハッシュは変化しているように見えますが、実際には新しいインスタンスです。
ジェフイエーツ

そうです、値タイプは不変なので、変更できません。良いキャッチ。
DavidN、2009年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.