6

在使用HashSetsC# 时,我最近遇到了一个烦人的问题:HashSets不保证元素的唯一性;它们不是集合。他们所做的保证是,当被调用时,如果集合中Add(T item)的任何项目是 ,则不会添加该项目。如果您操作集合中已有的项目,这将不再成立。一个演示的小程序(来自我的 Linqpad 的 copypasta):item.equals(that)true

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 的​​元素,并对事件采取行动以重新检查是否相等,但这似乎严重限制,更不用说引擎盖下的大量工作和性能损失.

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

4

3 回答 3

6

你真的在谈论对象身份。如果您要对项目进行哈希处理,它们需要具有某种身份,以便可以进行比较。

  • 如果情况发生变化,则它不是有效的身份方法。您目前拥有public int myint. 它确实应该是readonly,并且只能在构造函数中设置。
  • 如果两个对象在概念上是不同的(即您想在您的特定设计中将它们视为不同),那么它们的哈希码应该不同。
  • 如果您有两个具有相同内容的对象(即具有相同字段值的两个值对象),那么它们应该具有相同的哈希码并且应该相等。
  • 如果您的数据模型说您可以拥有两个具有相同内容但它们不能相等的对象,您应该使用代理 id,而不是散列内容。
  • 也许你的对象应该是不可变的值类型,所以对象不能改变
  • 如果它们是可变类型,您应该分配一个代理 ID(即一个从外部引入的,如递增的计数器 id 或使用对象的哈希码),对于给定的对象,它永远不会改变

这是您的Tester对象的问题,而不是集合的问题。您需要认真考虑如何定义身份。这不是一个容易的问题。

于 2012-07-10T10:13:27.623 回答
0

当我需要一个有保证的唯一项目的一维集合时,我通常会选择Dictionary<TKey, Tvalue>:你不能添加相同的元素Key,另外我通常需要为项目附加一些属性并且Value派上用场(我的首选值类型Tuple<>是许多价值观...)。

当然,它不是性能最高的解决方案,也不是最不占用内存的解决方案,但我通常没有性能/内存问题。

于 2012-07-10T10:18:15.040 回答
0

您应该实现自己的IEqualityComparer并将其传递给 HashSet 的构造函数,以确保您获得所需的相等比较器。

正如 Joe 所说,如果您希望集合保持唯一性,甚至超出 .Add(T item)您需要使用由构造函数创建且没有公开可见的集合属性的 ValueObjects。IE

于 2012-07-10T10:21:39.630 回答