HashSet不能移除一个对象

3

我可能在使用HashSet和HashCode时有所遗漏,但我不知道为什么它不像我想象的那样工作。我有一个对象,其中HashCode被覆盖。我将对象添加到HashSet中,然后更改一个属性(用于计算HashCode),然后我无法删除该对象。

public class Test 
{
    public string Code { get; set; }
    public override int GetHashCode()
    {
        return (Code==null)?0: Code.GetHashCode();
    }
}

public void TestFunction()
{
    var test = new Test();
    System.Collections.Generic.HashSet<Test> hashSet = new System.Collections.Generic.HashSet<Test>();
    hashSet.Add(test);
    test.Code = "Change";

    hashSet.Remove(test);  //this doesn´t remove from the hashset
}

4
好的,为您翻译:是的,你为什么期望其他行为?请务必阅读http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx。 - Alexei Levenkov
我建议删除你的 GetHashCode 实现,让 .NET Framework 为你生成它。只有在需要定义某种值等价性时才需要重写它。如果你想使用 Code 作为键,则应该使用 Dictionary<Code,OtherData> 而不是 HashSet<T> - Dai
null"Change"不同,这就是为什么HashSet无法再找到它的原因。Erip Lippert:“指南:GetHashCode返回的整数值不应该改变。理想情况下,可变对象的哈希码应该只从不可变的字段计算出来,因此对象的哈希值在其整个生命周期中都是相同的。” - Tim Schmelter
@TimSchmelter:不,这里没有涉及到空字符串,而且字符串相等性也不重要。Code的初始值为null,导致哈希码为0。如果“Change”的哈希码也恰好是0,那么它就会找到该值。 - Jon Skeet
1
你的名字是玛丽·史密斯,你把一个关于自己的文件放在一个标有“S”的文件夹里。然后你改名为玛丽·琼斯,并在一个名为“J”的文件夹中寻找该文件。该文件应该在以你的姓氏命名的文件夹中,那么为什么你找不到它呢?现在你明白了为什么你所做的是错误的,而且你绝不能这样做了吗?如果你改变哈希键,则必须在更改之前从字典中删除该项,然后再将其放回,就像你在更改姓名时必须将纸张从“S”移动到“J”一样。 - Eric Lippert
显示剩余4条评论
3个回答

9
首先,你重写了GetHashCode但是没有重写Equals。不要这样做。它们应该同时以一致的方式被重写。
接下来,你是正确的,改变对象的哈希码将会影响到任何基于哈希的数据结构中找到它的位置。毕竟,在基于哈希的结构中检查键是否存在的第一步是通过快速查找所有已存在具有相同哈希码的条目来找到候选相等值。数据结构无法“知道”哈希码已更改并更新其自身表示。 文档明确说明了这一点:
“对于不可变引用类型,你可以重写GetHashCode。通常情况下,对于可变引用类型,只有在以下情况下才应该重写 GetHashCode
  • 你可以从不可变字段计算哈希码;或者
  • 你可以确保可变对象的哈希码在包含依赖其哈希码的集合的对象时不会更改。

我没有编写等于方法,因为我想保持对我的问题的关注。但是感谢您的建议。 - Nacho
@user3759554:但Equals和GetHashCode本质上是相互关联的。如果您的实际实现包括它,那么您应该在问题中包含它。我赞赏保持代码集中的目的(真的,我很赞赏!),但这是一个有点特殊的情况 :) - Jon Skeet

2
public void TestFunction()
{
    var test = new Test();
    System.Collections.Generic.HashSet<Test> hashSet = new System.Collections.Generic.HashSet<Test>();
    test.Code = "Change";
    hashSet.Add(test);

    hashSet.Remove(test);  //this doesn´t remove from the hashset
}

首先将值设置到Test对象中,然后将其添加到HashSet中。


0

HashCode被用于在 HashSet / Dictionary 中查找对象。如果哈希码改变,那么这个对象就无法再在 HashSet 中被找到,因为通过新的HashSet查找具有新哈希码的项目的桶(很可能)不包含该对象(该对象在标记旧哈希码的桶中)。

请注意,在哈希码的初始搜索后,将使用 Equals 执行最终匹配,但它不适用于您的特定情况,因为您拥有相同的对象并使用默认Equals来比较引用。

详细说明/指南- GetHashCode的指南和规则 by Eric Lippert


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接