61

いつ、どのようにオーバーライドするかについて、約 10 の異なる質問を読みましたが、GetHashCodeまだよくわからないことがあります。のほとんどの実装はGetHashCode、オブジェクトのフィールドのハッシュ コードに基づいていますが、 の値はGetHashCodeオブジェクトの存続期間にわたって変更されるべきではないとされています。それが基づいているフィールドが可変である場合、それはどのように機能しますか? また、オーバーライドされたものではなく、参照の等価性に基づいて辞書検索などを行いたい場合はどうすればよいEqualsですか?

私は主にEquals、シリアル化と逆シリアル化 (私の場合は XML へ) によって参照の等価性が失われると想定しているシリアル化コードの単体テストを容易にするためにオーバーライドしているため、少なくとも値の等価性によって正しいことを確認したいと考えています。この場合、オーバーライドEqualsするのは悪い習慣ですか? 基本的に、実行中のコードのほとんどで、参照の等価性が必要であり、常に使用==しており、それをオーバーライドしていません。ValueEqualsオーバーライドする代わりに、新しいメソッドなどを作成する必要がありますEqualsか? 私は、フレームワークが常に物事を比較するために and を使用==しないと想定していたEqualsので、オーバーライドしても安全だと思っていEqualsました。==オペレーター。他のいくつかの質問を読むと、そうではないようです。

編集:

私の意図が不明確だったようです。私が言いたいのは、99% の時間は単純な古い参照の等価性、デフォルトの動作、驚きがないことを望んでいるということです。.Equals非常にまれなケースですが、値の等価性が必要であり、代わりに を使用して値の等価性を明示的に要求したいと考えています==

これを行うと、コンパイラはオーバーライドGetHashCodeも推奨するため、この質問が出てきました。GetHashCode変更可能なオブジェクトに適用する場合、次のような矛盾する目標があるように見えました。

  1. もしそうa.Equals(b)なら。a.GetHashCode()== b.GetHashCode()
  2. の値はa.GetHashCode()、 の存続期間中は変更されませんa

オブジェクトの状態が変化すると、 の値が変化することが予想されるため、これらは当然矛盾しているように.Equals()見えGetHashCodeます。.Equals()GetHashCode

なぜこのような矛盾があるように見えるのでしょうか? これらの推奨事項は、変更可能なオブジェクトに適用するためのものではありませんか? おそらく想定されていますが、構造体ではなくクラスについて言及していることに言及する価値があるかもしれません。

解像度:

私は JaredPar を承認済みとしてマークしていますが、主にコメントのやり取りのためです。ここから学んだことをまとめると、すべての目標を達成し、エッジ ケースで発生する可能性のある風変わりな動作を回避する唯一の方法は、オーバーライドEqualsGetHashCodeて不変フィールドに基づくか、または を実装することIEquatableです。Equalsこの種の参照型は、主キーでそれらを識別するためにリレーショナル データベースに格納されていない限り、ほとんどの参照型は通常、不変フィールドを持たないことがわかっているため、参照型のオーバーライドの有用性を損なうようです。

4

5 に答える 5

22

それが基づいているフィールドが可変である場合、それはどのように機能しますか?

オブジェクトが変更されるとハッシュ コードが変更されるという意味ではありません。これは、あなたが読んだ記事に記載されているすべての理由で問題です。残念ながら、これは通常、まれなケースでのみ現れるタイプの問題です。そのため、開発者は悪い振る舞いを回避する傾向があります。

また、オーバーライドされた Equals ではなく、参照の等価性に基づいて辞書検索などを行いたい場合はどうすればよいでしょうか?

このようなインターフェースを実装する限りIEquatable<T>、問題にはなりません。ほとんどのディクショナリ実装は、IEquatable<T>Object.ReferenceEquals を使用する方法で等値比較子を選択します。がなくてもIEquatable<T>、ほとんどの場合、デフォルトで Object.Equals() が呼び出され、実装に組み込まれます。

基本的に、実行中のコードのほとんどで参照の等価性が必要であり、常に == を使用しており、それをオーバーライドしていません。

オブジェクトが同等の値で動作すると予想される場合は、== と != をオーバーライドして、すべての比較で値が同等になるようにする必要があります。実際に参照の等価性が必要な場合、ユーザーは引き続き Object.ReferenceEquals を使用できます。

私は、フレームワークが物事を比較するために Equals ではなく常に == を使用すると想定していました。

BCL が使用するものは、時間の経過とともに少し変化しました。現在、同等性を使用するほとんどのケースはIEqualityComparer<T>インスタンスを取り、それを同等性に使用します。指定されていない場合は、検索に使用EqualityComparer<T>.Defaultされます。最悪の場合、これはデフォルトで Object.Equals を呼び出します。

于 2009-05-17T01:07:07.030 に答える
7

変更可能なオブジェクトがある場合、実際には使用できないため、GetHashCode メソッドをオーバーライドしてもあまり意味がありません。たとえば、DictionaryおよびHashSetコレクションによって、各アイテムをバケットに配置するために使用されます。コレクションでキーとして使用されているオブジェクトを変更すると、ハッシュ コードがオブジェクトが含まれているバケットと一致しなくなるため、コレクションが正しく機能せず、オブジェクトが二度と見つからない可能性があります。

ルックアップでクラスのGetHashCodeまたはメソッドを使用しないようにする場合は、.EqualsIEqualityComparerDictionary

このEqualsメソッドは値の等価性を目的としているため、そのように実装しても問題ありません。

于 2009-05-17T01:21:55.433 に答える
5

うわー、それは実際にはいくつかの質問が1つになっています:-)。したがって、次々と:

GetHashCode の値は、オブジェクトの存続期間にわたって変更されるべきではないことが引用されています。それが基づいているフィールドが可変である場合、それはどのように機能しますか?

この一般的なアドバイスは、オブジェクトを HashTable/dictionary などのキーとして使用する場合を対象としています。HashTables は通常、ハッシュを変更しないことを要求します。これは、ハッシュを使用してキーの保存方法と取得方法を決定するためです。ハッシュが変更された場合、HashTable はおそらくオブジェクトを見つけられなくなります。

Java のMapインターフェイスのドキュメントを引用するには:

注: 変更可能なオブジェクトをマップ キーとして使用する場合は、細心の注意を払う必要があります。オブジェクトがマップ内のキーであるときに、等値比較に影響を与える方法でオブジェクトの値が変更された場合、マップの動作は指定されません。

一般に、任意の可変オブジェクトをハッシュ テーブルのキーとして使用することはお勧めできません。キーがハッシュ テーブルに追加された後に変更された場合に何が起こるかは明らかではありません。ハッシュ テーブルは格納されたオブジェクトを古いキー、新しいキー、またはその両方を介して返す必要がありますか?

したがって、本当のアドバイスは次のとおりです。不変オブジェクトのみをキーとして使用し、それらのハッシュコードも変更されないようにしてください (オブジェクトが不変の場合、通常は自動的に変更されます)。

また、オーバーライドされた Equals ではなく、参照の等価性に基づいて辞書検索などを行いたい場合はどうすればよいでしょうか?

さて、そのように機能する辞書の実装を見つけてください。しかし、標準ライブラリの辞書は hashcode&Equals を使用しており、それを変更する方法はありません。

私は主に、シリアル化コードの単体テストを容易にするために Equals をオーバーライドしています。これは、シリアル化と逆シリアル化 (私の場合は XML へ) によって参照の等価性が失われると想定しているため、少なくとも値の等価性によって正しいことを確認したいと考えています。この場合、Equals をオーバーライドするのは悪い習慣ですか?

いいえ、それは完全に受け入れられると思います。ただし、そのようなオブジェクトは変更可能であるため、辞書/ハッシュテーブルのキーとして使用しないでください。上記を参照。

于 2009-05-17T01:13:39.463 に答える
2

私はC#については知りませんが、Javaではequals()をオーバーライドする場合、それらの間の契約を維持するためにhashCode()もオーバーライドする必要があります(逆も同様です)...そしてJavaまた、同じキャッチ 22 があります。基本的に不変フィールドの使用を強制します...しかし、これはハッシュキーとして使用されるクラスのみの問題であり、Javaにはすべてのハッシュベースのコレクションの代替実装があります...おそらくそれほど高速ではありませんが、効果的に機能します変更可能なオブジェクトをキーとして使用できるようにします...それは(通常)「貧弱な設計」として眉をひそめています。

そして、この根本的な問題は時代を超越していることを指摘したい衝動に駆られます... アダムが子供だった頃から存在していました.

私は、私よりも古い (私は 36 歳です) Fortran コードに取り組んできました。これは、ユーザー名が変更されると壊れます (女の子が結婚したり、離婚したときのように ;-) ... これがエンジニアリングです。採用された解決策は: GetHashCode「メソッド」は、以前に計算された hashCode を記憶し、hashCode (つまり、仮想 isDirty マーカー) を再計算し、キーフィールドが変更された場合は null を返します。これにより、キャッシュは「ダーティ」ユーザーを (別の GetPreviousHashCode を呼び出すことによって) 削除し、キャッシュは null を返し、ユーザーはデータベースから再読み取りを行います。興味深い、価値のあるハックです。自分で言っても(-_-;)

O(1) アクセス (すべての場合に望ましい) と、可変性 (まれな場合にのみ望ましい) をトレードオフします。エンジニアリングへようこそ。情報に基づく妥協の地。

乾杯。キース。

于 2009-05-17T06:57:17.377 に答える
1

ここでの基本的なトピックは、オブジェクトを最も一意に識別する方法です。そのプロセスで参照整合性が失われるため、重要なシリアル化/逆シリアル化について言及しました。

簡単に言うと、オブジェクトは、そのために使用できる不変フィールドの最小セットによって一意に識別される必要があるということです。これらは、GetHashCodeおよびEqualsをオーバーライドするときに使用する必要があるフィールドです。

テストの場合、必要なアサーションを定義することは完全に合理的です。通常、これらはタイプ自体ではなく、テストスイートのユーティリティメソッドとして定義されます。たぶんTestSuite.AssertEquals(MyClass、MyClass)?

GetHashCodeとEqualsは連携する必要があることに注意してください。GetHashCodeは、2つのオブジェクトが等しい場合、それらに対して同じ値を返す必要があります。Equalsは、2つのオブジェクトが同じハッシュコードを持っている場合にのみtrueを返す必要があります。(2つのオブジェクトが等しくない可能性がありますが、同じハッシュコードを返す可能性があることに注意してください)。このトピックに正面から取り組むウェブページはたくさんあります。

于 2009-05-17T01:35:10.300 に答える