如果你改变元素的身份,哈希集不会保持元素的唯一性



在C#中使用HashSets时,我最近遇到了一个令人讨厌的问题:HashSets不能保证元素的唯一性;它们不是集合。他们所保证的是,当调用Add(T item)时,如果集合item.equals(that)中的任何项目为true,则不添加该项目。如果操作集合中已经存在的项,则此选项将不再有效。一个演示的小程序(从我的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项目)添加项目的副本,枚举器枚举包含项目的副本?这就提出了一个挑战,即每个包含的元素都应该是深度可复制的,至少在影响其平等性的项目中是这样。

另一个解决方案是滚动自己的,并且只接受实现INotifyPropertyChanged的元素,并对事件采取行动来重新检查是否相等,但这似乎严重限制了,更不用说在后台会损失大量工作和性能了。

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

您实际上在谈论对象标识。如果你要散列项目,它们需要有某种身份,这样才能进行比较。

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

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

当我需要一个有保证的唯一项的一维集合时,我通常使用Dictionary<TKey, Tvalue>:你不能添加具有相同Key的元素,加上我通常需要为这些项附加一些属性,Value就派上了用场(对于许多值,我的首选值类型是Tuple<>…)。

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

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

正如Joe所说,如果您希望集合在.Add(T item)之后仍然保持唯一性,则需要使用由构造函数创建的ValueObjects,并且没有公开可见的集合属性。即

最新更新