C# hashset don';如果你改变了元素的身份,就不能保持元素的唯一性

C# hashset don';如果你改变了元素的身份,就不能保持元素的唯一性,c#,.net,clr,hashset,C#,.net,Clr,Hashset,在C#中使用hashset时,我最近遇到了一个恼人的问题:hashset不能保证元素的唯一性;它们不是布景。他们所做的保证是,当调用Add(T项)时,如果集合项中的任何项都是项,则不会添加该项。等于(即)为true。如果操作集合中已有的项,则此选项不再有效。一个小程序,演示(从我的Linqpad复制): void Main() { HashSet testset=新HashSet(); 添加(新测试仪(1)); 添加(新测试仪(2)); foreach(测试集中的测试仪){ tester.Dum

在C#中使用
hashset
时,我最近遇到了一个恼人的问题:
hashset
不能保证元素的唯一性;它们不是布景。他们所做的保证是,当调用
Add(T项)
时,如果集合
项中的任何项都是
项,则不会添加该项。等于(即)
true
。如果操作集合中已有的项,则此选项不再有效。一个小程序,演示(从我的Linqpad复制):

void Main()
{
HashSet testset=新HashSet();
添加(新测试仪(1));
添加(新测试仪(2));
foreach(测试集中的测试仪){
tester.Dump();
}
foreach(测试集中的测试仪){
tester.myint=3;
}
foreach(测试集中的测试仪){
tester.Dump();
}
HashSet secondhashset=新HashSet(testset);
foreach(第二个哈希集中的测试仪){
tester.Dump();
}
}
类测试员{
公共int-myint;
公共测试仪(int i){
this.myint=i;
}
公共覆盖布尔等于(对象o){
如果(o==null)返回false;
作为测试仪的测试仪=o;
如果(that==null)返回false;
return(this.myint==that.myint);
}
公共覆盖int GetHashCode(){
返回这个.myint;
}
公共重写字符串ToString(){
返回这个.myint.ToString();
}
}
它将愉快地操纵集合中的项,使其相等,只在构建新的哈希集时将它们过滤掉。当我想处理需要知道条目唯一性的集合时,什么是可建议的?Roll my own,其中Add(T item)添加项目的副本,枚举器枚举包含的项目的副本?这就提出了一个挑战,即每个包含的元素都应该是可深度复制的,至少在影响其平等性的项目中是如此

另一种解决方案是自行滚动,只接受实现INotifyPropertyChanged的元素,并对事件采取行动以重新检查平等性,但这似乎严重限制了性能,更不用说大量的工作和性能损失了


我想到的另一个可能的解决方案是确保构造函数中的所有字段都是只读或常量。所有的解决方案似乎都有很大的缺点。我还有其他选择吗?

你说的是对象标识。如果要对项目进行散列,它们需要具有某种标识,以便进行比较

  • 如果这种情况发生变化,则它不是有效的标识方法。您当前有
    public int myint
    。它实际上应该是只读的,并且只在构造函数中设置

  • 如果两个对象在概念上不同(即,您希望在特定设计中将它们视为不同的对象),那么它们的哈希代码应该不同
  • 如果有两个具有相同内容的对象(即两个具有相同字段值的值对象),则它们应该具有相同的哈希代码,并且应该相等
  • 如果您的数据模型说您可以有两个具有相同内容的对象,但它们不能相等,那么您应该使用代理id,而不是散列内容
  • 也许您的对象应该是不可变的值类型,这样对象就不能更改
  • 如果它们是可变类型,则应该为给定对象指定一个永远不会更改的代理项ID(即,外部引入的代理项ID,如递增的计数器ID或使用对象的哈希代码)

这是
测试仪
对象的问题,而不是集合的问题。你需要认真思考如何定义身份。这不是一个容易的问题。

当我需要一个保证唯一项的一维集合时,我通常会使用
字典
:您不能使用相同的
键添加元素,而且我通常需要将一些属性附加到项上,并且
值也很方便(对于许多值,我的go-to值类型为
Tuple


当然,它不是性能最好、占用内存最少的解决方案,但我通常不关心性能/内存。

您应该实现自己的解决方案,并将其传递给哈希集的构造函数,以确保获得所需的相等比较器

正如Joe所说,如果您希望集合在
.Add(T item)
之外保持唯一性,则需要使用构造函数创建的ValueObject,并且这些对象没有公共可见的集合属性。
i、 e.

我不确定我是否理解这个问题……你想知道哪种收集方式可以保证其中没有两个项目是相等的吗?蒂姆,是的,你可能需要滚动down@Martijn当前位置我忽略了
GetHashCode
。无论如何,他应该阅读Eric Lippert的博客:它解释了它的规则和指导原则。例如:“准则:GetHashCode返回的整数不应更改”和“规则:当对象包含在依赖于哈希代码保持稳定的数据结构中时,GetHashCode返回的整数不得更改”@Tim,谢谢!这正是问题所在。这也意味着一旦实例化并添加到集合中,对象就永远无法更改标识。真正的问题不在于哈希代码,而在于相等值。我可以将这个琐碎的问题更改为
public int GetHashCode(){return 0}
而hashcode将是不变的,但问题仍然是一样的。您涉及INotifyPropertyChanged的“滚动您自己的”解决方案指出了真正的问题:当集合中的一个元素发生变化,变成另一个元素的“相同”时,集合应该做什么?扔掉一个?哪一个?您需要先定义语义你可以寻找一个解决方案。因此,你可以采取某种选择3:只使用具有不可变标识的哈希集?你如何保证?这是一个完全不同的问题。请阅读以下内容:“如果你有多个具有给定标识的对象,那么它不是一个vali
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();
  }
}