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

在 a 中使用该对象时更改该对象的哈希码HashSet违反了HashSet的合同。

无法移除对象不是这里的问题。首先不允许您更改哈希码。

让我引用MSDN

只要确定对象的 Equals 方法的返回值的对象状态没有修改,对象的 GetHashCode 方法就必须始终返回相同的哈希码。请注意,这仅适用于应用程序的当前执行,并且如果再次运行应用程序,则可以返回不同的哈希码。

他们讲述的故事略有不同,但本质是相同的。他们说,哈希码永远不会改变。实际上,只要确保没有人再使用旧的哈希码,就可以更改它。并不是说这是一个好的做法,但它确实有效。

于 2012-08-07T14:52:25.107 回答
4

重要的是,添加到基于散列的表( 、 等)中的任何项目HashSetDictionary插入结构后都不会被修改(至少在它们被删除之前不会被修改)。

为了在数据结构中找到一个对象,它计算它的哈希码,然后根据该哈希码找到一个位置。如果你改变了那个对象,那么它返回的哈希码不再反映它在那个数据结构中的当前位置(除非你非常非常幸运并且它恰好是一个哈希冲突)。

字典的 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 回答