阿罗哈,
这是一个覆盖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);
}
答案 0 :(得分:4)
在HashSet
中使用该对象时,更改对象的哈希码违反了HashSet
的合同。
无法删除对象不是问题所在。 您不能首先更改哈希码。
让我引用MSDN:
对象的GetHashCode方法必须始终返回相同的内容 哈希码只要没有对象状态的修改即可 确定对象的Equals方法的返回值。注意 这仅适用于当前执行的应用程序,并且 如果运行应用程序,则可以返回不同的哈希代码 试。
他们讲的故事略有不同,但实质是一样的。他们说,哈希码可以从不改变。实际上,只要确保没有人再使用旧的哈希码,您就可以对其进行更改。并不是说这是好习惯,但它确实有效。
答案 1 :(得分:4)
重要的是,添加到基于散列的表(HashSet
,Dictionary
等)的任何项目在插入结构后都不会被修改(至少在它们被删除之前不会被修改)。
要在数据结构中查找对象,它会计算哈希代码,然后根据该哈希代码查找位置。如果你改变那个对象,那么它返回的哈希码不再反映它在该数据结构中的当前位置(除非你非常非常幸运,它恰好是一个哈希冲突)。
只要一个对象被用作
Dictionary<TKey, TValue>
中的一个键,就不能以任何影响其哈希值的方式进行更改。
同样的断言同样适用于HashSet
,因为它们都是使用哈希表实现的。
答案 2 :(得分:2)
这里有很好的答案,只是想加上这个。如果您查看经过反编译的HashSet<T>
代码,您会看到Add(value)
执行以下操作:
IEqualityComparer<T>.GetHashCode()
获取值的哈希码。对于默认比较器,归结为GetHashCode()
。当您致电Remove(value)
时,它会执行步骤1.和2.再次查找引用所在的位置。然后它调用IEqualityComparer<T>.Equals()
以确保它确实找到了正确的值。但是,由于您已更改GetHashCode()
返回的内容,因此会计算不同的存储桶/插槽位置,这是无效的。因此,它无法找到对象。
所以,请注意Equals()
并没有真正发挥作用,因为如果哈希码发生变化,它甚至永远不会到达正确的存储桶/插槽位置。