回答:
答えはほとんどが、有効なガイドラインですが、おそらく有効なルールではありません。それはまた、全体の話をしません。
重要な点は、2つの等しいオブジェクトは同じハッシュコードを返す必要があり、ハッシュコードはオブジェクトの存続期間中有効でなければならないため、可変型の場合、ハッシュコードを可変データに基づくことはできないということです。ハッシュコードが変更されると、オブジェクトは正しいハッシュビンに存在しなくなるため、ハッシュされたコレクションで失われることになります。
たとえば、オブジェクトAは1のハッシュを返すため、ハッシュテーブルのビン1に入ります。次に、オブジェクトAを変更して、ハッシュ2を返すようにします。ハッシュテーブルがハッシュを探しに行くと、オブジェクトはビン2にあり、それを見つけることができません。オブジェクトはビン1に孤立しています。これが、ハッシュコードが必要な理由です。オブジェクトの存続期間中は変更しないでください。GetHashCode実装を作成するのがお尻の苦痛である理由の1つにすぎません。
更新
Eric Lippertが、に関する優れた情報を提供するブログを投稿しましたGetHashCode
。
追加の更新
上記の変更をいくつか行いました。
ガイドラインは単なるガイドであり、ルールではありません。実際にGetHashCode
は、オブジェクトがハッシュテーブルに格納されている場合など、オブジェクトがガイドラインに従うことが期待される場合にのみ、これらのガイドラインに従う必要があります。ハッシュテーブル(またはの規則に依存するその他のもの)でオブジェクトを使用する予定GetHashCode
がない場合、実装はガイドラインに従う必要はありません。
「オブジェクトの存続期間中」が表示された場合は、「オブジェクトがハッシュテーブルと連携する必要がある時間」などを読んでください。ほとんどのGetHashCode
場合と同様に、ルールを破るタイミングを知ることです。
久しぶりですが、なぜか、どういうことかなど、この質問には正しい答えが必要だと思います。これまでの最良の答えは、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固有ではありません-オブジェクトがリスト内にある間は、オブジェクトの識別データが変更されないのは、すべてのハッシュテーブル実装、またはより一般的にはインデックス付きリストの性質によるものです。 。このルールに違反すると、予期しない動作や予期しない動作が発生します。リスト内のすべての要素を監視し、リストの自動再インデックスを実行するリスト実装がどこかにある可能性があります。
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に関するドキュメントは少しわかりにくいと思います。
一方では、MSDNは、オブジェクトのハッシュコードは決して変更されず、一定であることを述べています。他方、MSDNは、GetHashcodeの戻り値は、2つのオブジェクトが等しいと見なされた場合、それらも等しいはずであるとも述べています。
ハッシュ関数には次のプロパティが必要です。
- 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(エンティティ)からロードして、そのハッシュコードを取得するとします。特定の数を返します。アプリケーションを閉じ、同じエンティティをロードします。今回のハッシュコードは、エンティティを最初にロードしたときと同じ値である必要がありますか?私見ではありません。
これは良いアドバイスです。ブライアンペピンはこの件について次のように述べています。
これにより、私は2回以上失敗しました。GetHashCodeがインスタンスの存続期間を通じて常に同じ値を返すことを確認してください。ハッシュコードは、ほとんどのハッシュテーブル実装で「バケット」を識別するために使用されることに注意してください。オブジェクトの「バケット」が変更されると、ハッシュテーブルがオブジェクトを見つけられない場合があります。これらは見つけるのが非常に難しいバグになる可能性があるので、最初は正しく修正してください。
X
、またはY
一度X.Equals(Y)
またはY.Equals(X)
呼び出された後のすべての呼び出しで同じ結果が得られるように指定しました。他の同等性の定義を使用したい場合は、を使用しEqualityComparer<T>
ます。
マーク・ブルックスのこのブログ投稿をチェックしてください:
その後、フォローアップの投稿(私は新しいのでリンクできませんが、最初の記事にリンクがあります)をチェックしてください。この記事では、初期の実装におけるいくつかのマイナーな弱点についてさらに詳しく説明しています。
これは、GetHashCode()実装の作成について知っておく必要があるすべてのことでした。彼は、他のユーティリティと一緒に自分のメソッドのダウンロードも提供しています。
ハッシュコードは変更されませんが、ハッシュコードがどこから来ているのかを理解することも重要です。
オブジェクトが値のセマンティクスを使用している場合、つまりオブジェクトのIDはその値(文字列、色、すべての構造体など)によって定義されます。オブジェクトのIDがそのすべての値から独立している場合、ハッシュコードはその値のサブセットによって識別されます。たとえば、StackOverflowエントリはデータベースのどこかに保存されています。名前または電子メールを変更しても、一部の値は変更されていますが、顧客のエントリは同じままです(最終的には、通常、長い顧客ID#によって識別されます)。
要するに:
値タイプのセマンティクス-ハッシュコードは値によって定義されます参照タイプのセマンティクス-ハッシュコードはいくつかのIDによって定義されます
これがまだ意味をなさない場合は、Eric EvansによるDomain Driven Designを読んで、エンティティと値の型(多かれ少なかれ、私が上で試みたものです)を読むことをお勧めします。
Eric LippertによるGetHashCodeのガイドラインとルールを確認してください