13

インターネット上には何百もの同様の質問が投稿されているため、あなたがすべて怒ってしまう前に、私は最後の数時間を費やしてそれらすべてを読んだだけで、私の質問に対する答えが見つからなかったことを保証できます.

バックグラウンド:

基本的に、私の大規模なアプリケーションの 1 つは、現在選択されている項目を編集した後Bindingに、プロパティの一部の が機能しなくなっListBox.SelectedItemたり、プログラムがクラッシュしたりする状況に悩まされていました。最初に、「同じキーを持つアイテムが既に追加されています」コードの質問から ListBoxItem を選択する際の例外をここで尋ねましたが、回答がありませんでした。

その問題を解決するために数日与えられた今週まで、私はその問題に取り組む時間がありませんでした。簡単に言うと、問題の原因がわかりました。これは、データ型クラスがEqualsメソッドをオーバーライドしたため、GetHashCodeメソッドもオーバーライドされたためです。

この問題に気付いていない人のために、不変のフィールド/プロパティGetHashCodeを使用してのみメソッドを実装できることを発見しました。Overriding GetHashCode()投稿に対する Harvey Kwok の回答からの抜粋を使用して、これを説明します。

問題は、GetHashCode が Dictionary および HashSet コレクションによって使用され、各項目をバケットに配置することです。いくつかの変更可能なフィールドに基づいてハッシュコードが計算され、オブジェクトが HashSet または Dictionary に配置された後にフィールドが実際に変更された場合、そのオブジェクトは HashSet または Dictionary から見つけることができなくなります。

メソッドで変更可能なプロパティを使用したため、実際の問題が発生しました。ユーザーが UI でこれらのプロパティ値を変更すると、オブジェクトに関連付けられたハッシュ コード値が変更され、アイテムがコレクション内で見つからなくなりました。GetHashCode

質問:

それで、私の質問は、GetHashCode不変フィールドのないクラスにメソッドを実装する必要がある状況を処理する最良の方法は何ですか? 申し訳ありませんが、その質問以前に尋ねられたので、より具体的にさせてください。

Overriding GetHashCode()投稿の回答は、これらの状況では、単に定数値を返す方がよいことを示唆しています...値を返すことを提案する人もいれば1、素数を返すことを提案する人もいます。個人的には、これらの提案に違いは見られません。なぜなら、どちらにも使用されるバケットは 1 つしかないと思っていたからです。

さらに、 Eric Lippert のブログにあるGetHashCode のガイドラインとルールの記事には、「ガイドライン: ハッシュ コードの配布は「ランダム」でなければならない」というセクションがあり、十分なバケットが使用されない結果となるアルゴリズムを使用する場合の落とし穴が強調されています。彼は、使用するバケットの数を減らし、バケットが非常に大きくなるとパフォーマンスの問題を引き起こすアルゴリズムについて警告しています。確かに、定数を返すことはこのカテゴリに分類されます。

Guidすべてのデータ型クラス (データベースではなく C# のみ) に特別なフィールドを追加して、特にGetHashCodeメソッド内でのみ使用するというアイデアがありました。この長い紹介の最後に、私の実際の質問は、どちらの実装が優れているかということだと思います。要約すると:

概要:

不変フィールドのないクラスで Object.GetHashCode() をオーバーライドする場合、メソッドから定数を返すか、メソッドでのみ使用するためにクラスごとGetHashCodeに追加のフィールドを作成する方が良いですか? 新しいフィールドを追加する必要がある場合、どのタイプにする必要がありますか?メソッドに含める必要はありませんか?readonlyGetHashCodeEquals

誰からの回答も喜んで受け取りますが、このテーマについて十分な知識を持つ上級開発者から回答を受け取ることを本当に望んでいます。

4

5 に答える 5

16

基本に戻る。あなたは私の記事を読んだ。もう一度読んでください。あなたの状況に関連する2つの鉄壁のルールは次のとおりです。

  • x が y と等しい場合、x のハッシュ コードは y のハッシュ コードと等しくなければなりません。同等: x のハッシュ コードが y のハッシュ コードと等しくない場合、x と y は等しくない必要があります。
  • x がハッシュ テーブルにある間、x のハッシュ コードは安定したままでなければなりません。

これらは正確さの要件です。これら 2 つの単純なことを保証できない場合、プログラムは正しくありません。

あなたは 2 つの解決策を提案します。

最初の解決策は、常に定数を返すことです。これは両方のルールの要件を満たしていますが、ハッシュ テーブルでの線形検索に限定されます。リストを使用することもできます。

あなたが提案する他の解決策は、オブジェクトごとにハッシュコードを何らかの方法で生成し、それをオブジェクトに格納することです。等しいアイテムのハッシュ コードが等しい場合、これは完全に合法です。それを行うと、ハッシュコードが異なる場合、 xy に等しいという制限があります。これにより、値の等価性は基本的に不可能になるようです。参照の等価性が必要な場合、そもそも Equals をオーバーライドしないため、これは非常に悪い考えのように思えますが、equals が一貫している場合は合法です。

ハッシュテーブルはそもそも間違ったデータ構造であるため、オブジェクトをハッシュテーブルに決して置かないという3番目の解決策を提案します。ハッシュ テーブルのポイントは、「この値は、この不変値のセットにあるのか?」という質問にすばやく答えることにあります。不変の値のセットがないため、ハッシュ テーブルを使用しないでください。仕事に適したツールを使用してください。リストを使用して、直線的な検索を行うという苦痛に耐えてください。

4 番目の解決策は次のとおりです。等価性のために使用される変更可能なフィールドをハッシュし、オブジェクトを変更するたびに直前にあるすべてのハッシュ テーブルからオブジェクトを削除し、後で元に戻します。これは両方の要件を満たしています: ハッシュ コードは等価性に一致し、ハッシュ テーブル内のオブジェクトのハッシュは安定しており、高速なルックアップが得られます。

于 2013-11-01T05:41:58.773 に答える
0

クラスにハッシュ値を計算できる定数が本当に含まれていない場合は、GUID よりも単純なものを使用します。クラス(またはラッパークラス)に永続化された乱数を使用するだけです。

于 2013-10-31T15:13:00.370 に答える
0

簡単な方法は、hashCode をプライベート メンバーに格納し、最初の使用時に生成することです。エンティティが頻繁に変更されず、等しい (Equals メソッドが true を返す) 2 つの異なるオブジェクトをディクショナリのキーとして使用しない場合は、これで問題ありません。

private int? _hashCode;

public override int GetHashCode() {
   if (!_hashCode.HasValue)
      _hashCode = Property1.GetHashCode() ^ Property2.GetHashCode() etc... based on whatever you use in your equals method
   return _hashCode.Value;
}

ただし、たとえば、a.Equals(b) == true のオブジェクト a とオブジェクト b があり、a をキーとして使用して辞書にエントリを格納するとします (辞書[a] = 値)。
a が変更されない場合、dictionary[b] は値を返しますが、エントリを辞書に格納した後に a を変更すると、dictionary[b] は失敗する可能性が高くなります。これに対する唯一の回避策は、キーのいずれかが変更されたときに辞書を再ハッシュすることです。

于 2013-10-31T15:23:29.803 に答える