HashSet <t> .RemoveWhere()和GetHashCode()</t>

时间:2012-08-07 14:49:44

标签: c#

阿罗哈,

这是一个覆盖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中得到了那个对象?我错过了什么,这是从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);
    }

3 个答案:

答案 0 :(得分:4)

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

无法删除对象不是问题所在。 您不能首先更改哈希码。

让我引用MSDN

  

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

他们讲的故事略有不同,但实质是一样的。他们说,哈希码可以从不改变。实际上,只要确保没有人再使用旧的哈希码,您就可以对其进行更改。并不是说这是好习惯,但它确实有效。

答案 1 :(得分:4)

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

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

MSDN page for Dictionary上说:

  

只要一个对象被用作Dictionary<TKey, TValue>中的一个键,就不能以任何影响其哈希值的方式进行更改。

同样的断言同样适用于HashSet,因为它们都是使用哈希表实现的。

答案 2 :(得分:2)

这里有很好的答案,只是想加上这个。如果您查看经过反编译的HashSet<T>代码,您会看到Add(value)执行以下操作:

  1. 调用IEqualityComparer<T>.GetHashCode()获取值的哈希码。对于默认比较器,归结为GetHashCode()
  2. 使用该哈希码计算应存储(引用)值的“bucket”和“slot”。
  3. 存储参考。
  4. 当您致电Remove(value)时,它会执行步骤1.和2.再次查找引用所在的位置。然后它调用IEqualityComparer<T>.Equals()以确保它确实找到了正确的值。但是,由于您已更改GetHashCode()返回的内容,因此会计算不同的存储桶/插槽位置,这是无效的。因此,它无法找到对象。

    所以,请注意Equals()并没有真正发挥作用,因为如果哈希码发生变化,它甚至永远不会到达正确的存储桶/插槽位置。

相关问题