5

アロハ、

GetHashCode をオーバーライドする単純なクラスを次に示します。

class OverridesGetHashCode
{
    public string Text { get; set; }

    public override int GetHashCode()
    {
        return (Text != null ? Text.GetHashCode() : 0);
    }
    // overriding Equals() doesn't change anything, so I'll leave it out for brevity
}

そのクラスのインスタンスを作成したら、それを HashSet に追加し、その Text プロパティを次のように変更します。

var hashset = new HashSet<OverridesGetHashCode>();
var oghc = new OverridesGetHashCode { Text = "1" };
hashset.Add(oghc);
oghc.Text = "2";

次に、これは機能しません:

var removedCount = hashset.RemoveWhere(c => ReferenceEquals(c, oghc));
// fails, nothing is removed
Assert.IsTrue(removedCount == 1);

どちらもこれをしません:

// this line works, i.e. it does find a single item matching the predicate
var existing = hashset.Single(c => ReferenceEquals(c, oghc));
// but this fails; nothing is removed again
var removed = hashset.Remove(existing);
Assert.IsTrue(removed); 

内部で使用するハッシュは、アイテムが挿入されたときに生成されると思います。それが本当なら、 hashset.Contains(oghc) が機能しないことは理解できます。また、ハッシュコードでアイテムを検索し、一致が見つかった場合にのみ述語をチェックすると思います。それが最初のテストが失敗する理由かもしれません(ここでも推測しています)。しかし、最後のテストが失敗するのはなぜですか?ハッシュセットからそのオブジェクトを取得しただけなのでしょうか? HashSet から何かを削除する方法が間違っていますか?

これを読んでいただきありがとうございます。

更新: 混乱を避けるために、Equals() は次のとおりです。

protected bool Equals(OverridesGetHashCode other)
    {
        return string.Equals(Text, other.Text);
    }

public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((OverridesGetHashCode) obj);
    }
4

3 に答える 3

4

オブジェクトがで使用されている間にオブジェクトのハッシュコードを変更することは、の契約HashSetに違反します。HashSet

ここでは、オブジェクトを削除できないことは問題ではありません。そもそもハッシュコードを変更することは許可されていません。

MSDNから引用させてください:

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

彼らは少し違った話をしますが、本質は同じです。彼らは、ハッシュコードは決して変更できないと言います。実際には、古いハッシュコードを誰も使用しないようにする限り、変更できます。これは良い習慣ではありませんが、機能します。

于 2012-08-07T14:52:25.107 に答える
4

ハッシュベースのテーブル(、など)に追加されたアイテムは、構造に挿入された後(少なくとも削除されるまで)変更されないことが重要HashSetですDictionary

データ構造内のオブジェクトを見つけるために、ハッシュコードを計算し、そのハッシュコードに基づいて場所を見つけます。そのオブジェクトを変更すると、返されるハッシュコードは、そのデータ構造内の現在の場所を反映しなくなります(非常に幸運で、たまたまハッシュの衝突である場合を除きます)。

辞書のMSDNページには次のように書かれています。

オブジェクトがのキーとして使用されている限り、Dictionary<TKey, TValue>そのハッシュ値に影響を与えるような方法でオブジェクトを変更してはなりません。

HashSetどちらもハッシュテーブルを使用して実装されているため、これと同じアサーションが適用されます。

于 2012-08-07T14:54:01.187 に答える
2

ここには良い答えがあり、これを追加したかっただけです。逆コンパイルされたコードを見ると、次のHashSet<T>ことがわかります。Add(value)

  1. IEqualityComparer<T>.GetHashCode()値のハッシュコードを取得するために呼び出します。デフォルトの比較器の場合、これは要約するとGetHashCode()
  2. そのハッシュコードを使用して、(参照)値を格納する「バケット」と「スロット」を計算します。
  3. 参照を格納します。

呼び出すRemove(value)と、ステップ1と2が再度実行され、参照がどこにあるかがわかります。IEqualityComparer<T>.Equals()次に、それが実際に正しい値を見つけたことを確認するために呼び出します。ただし、GetHashCode()返されるものを変更したため、別のバケット/スロットの場所が計算されますが、これは無効です。したがって、オブジェクトを見つけることができません。

Equals()したがって、ハッシュコードが変更された場合、正しいバケット/スロットの場所に到達することさえないため、ここでは実際には機能しないことに注意してください。

于 2012-08-07T15:01:12.630 に答える