在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的元素,并对事件采取行动重新检查是否相等,但这似乎是严重限制,更不用说大量的工作和性能损失引擎盖下。
我想到的另一个可能的解决方案是确保构造函数中的所有字段都是readonly或const。所有解决方案似乎都有很大的缺点。我还有其他选择吗?
答案 0 :(得分:6)
你真的在谈论对象身份。如果您要哈希项目,他们需要具有某种身份,以便进行比较。
public int myint
。它应该是readonly
,并且只在构造函数中设置。这是Tester
个对象的问题,而不是集合。你需要认真思考如何定义身份。这不是一个容易的问题。
答案 1 :(得分:0)
当我需要保证唯一项目的一维集合时,我通常使用Dictionary<TKey, Tvalue>
:您不能添加具有相同Key
的元素,而且我通常需要将一些属性附加到项目和Value
派上用场(对于许多值,我的首选值类型为Tuple<>
。)。
当然,它不是性能最高也不是最不需要内存的解决方案,但我通常不会遇到性能/内存问题。
答案 2 :(得分:0)
您应该实现自己的IEqualityComparer并将其传递给HashSet的构造函数,以确保获得所需的相等比较器。
正如Joe所说,如果你希望集合在.Add(T item)
之后保持唯一,你需要使用由构造函数创建的ValueObjects,并且没有公开可见的集合属性。
即。