138

Essential C# 3.0 and .NET 3.5 book を読みました:

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

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

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

4

9 に答える 9

122

長くなってしまいましたが、それでもなお、なぜ、どのように、という説明も含めて、この問いに正確な答えを出す必要があると思います。これまでの最良の答えは、MSDN を徹底的に引用したものです。独自のルールを作成しようとしないでください。MS の担当者は、自分たちが何をしているかを知っていました。

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

理由は2つあります

最初の理由: ハッシュコードが、オブジェクト自体が変更されたとしても、オブジェクトの有効期間中に変更されない方法で計算された場合、それは等号契約を破るよりも.

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

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

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

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

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

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

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


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

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

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

キー オブジェクトは、Hashtable でキーとして使用される限り、不変でなければなりません。

これは次のことを意味します。

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

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

次に、オブジェクトに「you are hashed now」フラグを付けます。すべてのオブジェクト データが非公開であることを確認し、オブジェクト データを変更できるすべての関数のフラグをチェックし、変更が許可されていない場合 (つまり、フラグが設定されている場合) は例外データをスローします。 )。ここで、オブジェクトをハッシュ領域に配置するときは、必ずフラグを設定し、不要になったらフラグを設定解除してください。使いやすくするために、「GetHashCode」メソッド内でフラグを自動的に設定することをお勧めします。この方法では、フラグを忘れることはありません。また、「ResetHashFlag」メソッドの明示的な呼び出しにより、プログラマーは、オブジェクト データの変更が今までに許可されているか許可されていないかを考える必要があります。

わかりました、同様に言わなければならないこと: オブジェクトのデータが変更されたときに、等号とハッシュコードの契約に違反することなく、変更可能なデータを持つオブジェクトを持つことが可能であるにもかかわらず、ハッシュコードが変更されない場合があります。

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

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

(ちなみに: これはすべて C# や .NET 固有のものではありません。すべてのハッシュテーブルの実装、またはより一般的にはインデックス付きリストの性質上、オブジェクトがリストにある間、オブジェクトの識別データは決して変更されるべきではありません。 . この規則が破られた場合, 予期せぬ予期しない動作が発生します. どこかに, リスト内のすべての要素を監視し, リス​​トの自動再索引付けを行うリストの実装があるかもしれません.

于 2010-07-13T08:53:24.047 に答える
9

MSDNから

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

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

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

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


編集

また、MSDNから:

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

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

キー オブジェクトは、Hashtable でキーとして使用される限り、不変でなければなりません。

私がこれを読んだ方法は、変更可能なオブジェクトは、ハッシュテーブルで使用するように設計されていない限り、値が変化すると異なるハッシュコードを返す必要があるということです。

System.Drawing.Point の例では、オブジェクトは変更可能であり X または Y の値が変化すると異なるハッシュコードを返します。これにより、ハッシュテーブルでそのまま使用するのは適していません。

于 2009-01-20T18:29:42.903 に答える
5

Marc Brooks のこのブログ投稿をご覧ください。

VTO、RTO、および GetHashCode() -- なんてこった!

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

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

于 2010-02-19T10:35:24.133 に答える
4

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

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

要するに:

値型セマンティクス - ハッシュコードは値によって定義されます 参照型セマンティクス - ハッシュコードは ID によって定義されます

それでも意味が分からない場合は、Eric Evans による Domain Driven Design を読むことをお勧めします。ここでは、エンティティと値の型について説明しています (これは、私が上で試みたこととほぼ同じです)。

于 2009-01-20T18:45:04.777 に答える
3

Eric Lippert によるGetHashCode のガイドラインとルールを確認してください。

于 2011-03-01T13:34:25.643 に答える