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

Vie*_*orm 5 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
}
Run Code Online (Sandbox Code Playgroud)

当我创建该类的实例时,将其添加到HashSet然后更改其Text属性,如下所示:

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

那么这不起作用:

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

这两个都没有:

// 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); 
Run Code Online (Sandbox Code Playgroud)

我猜它内部使用的哈希是在插入项时生成的,如果这是真的,那么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);
    }
Run Code Online (Sandbox Code Playgroud)

Jon*_*art 2

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

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

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

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