在 C# 中使用 HashSets
时,我最近遇到了一个烦人的问题:HashSets
不保证元素的唯一性;他们不是集合。他们所做的保证是,当 Add(T item)
被调用时,如果集合中的任何项目 item.equals(that)
是 则不会添加该项目真
。如果您操作集合中已有的项目,这将不再适用。一个演示的小程序(copypasta from my Linqpad):
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 的元素,并对事件采取行动以重新检查是否相等,但这似乎有严重的局限性,更不用说大量的工作和性能损失在幕后。
我想到的另一个可能的解决方案是确保构造函数中的所有字段都是只读的或常量的。所有解决方案似乎都有非常大的缺点。我还有其他选择吗?
最佳答案
您实际上是在谈论对象标识。如果您要对项目进行哈希处理,则它们需要具有某种身份以便进行比较。
- 如果发生变化,则它不是有效的标识方法。您当前拥有
public int myint
。它确实应该是readonly
,并且只能在构造函数中设置。 - 如果两个对象在概念上不同(即您希望在您的特定设计中将它们视为不同),那么它们的哈希码应该不同。
- 如果您有两个具有相同内容的对象(即具有相同字段值的两个值对象),那么它们应该具有相同的哈希码并且应该相等。
- 如果您的数据模型表明您可以拥有两个具有相同内容的对象,但它们不能相等,则您应该使用代理 ID,而不是哈希内容。
- 也许你的对象应该是不可变的值类型,这样对象就不会改变
- 如果它们是可变类型,您应该分配一个代理项 ID(即从外部引入的一个,例如递增计数器 ID 或使用对象的哈希码),该 ID 对于给定对象永远不会改变
这是您的 Tester
对象的问题,而不是集合的问题。您需要认真考虑如何定义身份。这不是一个简单的问题。
关于c# - 如果你改变它们的身份,HashSets 不会保持元素的唯一性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11410994/