繁体   English   中英

HashSet的 <T> .RemoveWhere()和GetHashCode()

[英]HashSet<T>.RemoveWhere() and GetHashCode()

阿罗哈,

这是一个覆盖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);
    }

通过在HashSet中使用该对象时更改对象的哈希码是违反HashSet的契约。

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

让我引用MSDN

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

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

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

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

词典MSDN页面上说:

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

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

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

  1. 调用IEqualityComparer<T>.GetHashCode()来获取值的哈希码。 对于默认的比较器,这归结为GetHashCode()
  2. 使用该哈希码来计算应该存储(引用)值的“桶”和“槽”。
  3. 存储参考。

当您调用Remove(value)它会执行步骤1.和2.再次查找引用所在的位置。 然后它调用IEqualityComparer<T>.Equals()以确保它确实找到了正确的值。 但是,由于您已更改GetHashCode()返回的内容,因此它会计算不同的存储桶/插槽位置,这是无效的。 因此,它找不到对象。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM