【发布时间】:2012-07-09 19:01:17
【问题描述】:
在 C# 中使用 HashSets 时,我最近遇到了一个烦人的问题:HashSets 不保证元素的唯一性;它们不是集合。他们所做的保证是,当Add(T item) 被调用时,如果集合中的任何项目item.equals(that) 是true,则不会添加该项目。如果您操作集合中已有的项目,这将不再成立。一个演示的小程序(来自我的 Linqpad 的 copypasta):
void Main()
{
HashSet<Tester> testset = new HashSet<Tester>();
testset.Add(new Tester(1));
testset.Add(new Tester(2));
foreach(Tester tester in testset){
tester.Dump();
}
foreach(Tester tester in testset){
tester.myint = 3;
}
foreach(Tester tester in testset){
tester.Dump();
}
HashSet<Tester> secondhashset = new HashSet<Tester>(testset);
foreach(Tester tester in secondhashset){
tester.Dump();
}
}
class Tester{
public int myint;
public Tester(int i){
this.myint = i;
}
public override bool Equals(object o){
if (o== null) return false;
Tester that = o as Tester;
if (that == null) return false;
return (this.myint == that.myint);
}
public override int GetHashCode(){
return this.myint;
}
public override string ToString(){
return this.myint.ToString();
}
}
它会很高兴地将集合中的项目操作为相等,仅在构建新的 HashSet 时将它们过滤掉。当我想使用需要知道条目是唯一的集合时,有什么建议?滚动我自己的,其中 Add(T item) 添加项目的副本,而枚举器枚举所包含项目的副本?这带来了挑战,即每个包含的元素都应该是可深度复制的,至少在影响其平等的项目中是这样。
另一种解决方案是自行推出,只接受实现 INotifyPropertyChanged 的元素,并对事件采取行动以重新检查是否相等,但这似乎严重限制,更不用说大量工作和性能损失在引擎盖下。
我想到的另一个可能的解决方案是确保构造函数中的所有字段都是只读的或 const 的。所有解决方案似乎都有很大的缺点。我还有其他选择吗?
【问题讨论】:
-
我不确定我是否理解这个问题...您想知道哪种集合可以保证其中没有两个项目是相等的?
-
蒂姆,确实如此,您可能需要向下滚动
-
@Martijn:我忽略了
GetHashCode。无论如何,他应该阅读 Eric Lippert 的这篇博客:blogs.msdn.com/b/ericlippert/archive/2011/02/28/… 它解释了它的规则和指导方针。例如:“准则:GetHashCode 返回的整数永远不应该改变”和“规则:当对象包含在依赖于哈希码保持稳定” -
@Tim,谢谢!这几乎就是事情出错的地方。这也意味着一旦实例化并添加到集合中,对象就永远不能改变身份。真正的问题不在于哈希码,而在于等号。我可以将其更改为
public int GetHashCode() { return 0 }并且哈希码将保持不变,但问题仍然存在。 -
您涉及
INotifyPropertyChanged的“自己动手”解决方案指出了这里的真正问题:当其中一个元素突变为与另一个元素“相同”时,该集合应该怎么做?扔一个?哪一个?您需要先定义语义,然后才能寻找解决方案。