如何强制HashSet重新成员?

时间:2018-06-08 17:24:16

标签: c# hashset iequalitycomparer

在这种情况下,编辑一个成员以使其与另一个成员相等,强制HashSet重新计算哈希值并从而清除重复项的正确方法是什么?

我知道最好不要期望这会自动发生,所以我尝试了将HashSet与自身相交的事情,然后将其重新分配给引用自身和相同EqualityComparer的构造函数调用。我确信后者会奏效,但不会。

成功的一件事是将HashSet从其转换为其他容器类型(如List)重构,而不是直接从它自身。

班级定义:

public class Test {
    public int N;
    public override string ToString() { return this.N.ToString(); }
    }
public class TestClassEquality: IEqualityComparer<Test> {
    public bool Equals(Test x, Test y) { return x.N == y.N; }
    public int GetHashCode(Test obj) { return obj.N.GetHashCode(); }
    }

测试代码:

    TestClassEquality eq = new TestClassEquality();
    HashSet<Test> hs = new HashSet<Test>(eq);
    Test a = new Test { N = 1 }, b = new Test { N = 2 };
    hs.Add(a);
    hs.Add(b);
    b.N = 1;
    string fmt = "Count = {0}; Values = {1}";
    Console.WriteLine(fmt, hs.Count, string.Join(",", hs));
    hs.IntersectWith(hs);
    Console.WriteLine(fmt, hs.Count, string.Join(",", hs));
    hs = new HashSet<Test>(hs, eq);
    Console.WriteLine(fmt, hs.Count, string.Join(",", hs));
    hs = new HashSet<Test>(new List<Test>(hs), eq);
    Console.WriteLine(fmt, hs.Count, string.Join(",", hs));

输出:

"Count: 2; Values: 1,1"
"Count: 2; Values: 1,1"
"Count: 2; Values: 1,1"
"Count: 1; Values: 1"

基于最后的方法成功,我可能会创建一个扩展方法,其中HashSet将自身转储到本地List中,清除自身,然后从所述列表重新填充。

这是真的有必要还是有一些更简单的方法来做到这一点?

3 个答案:

答案 0 :(得分:9)

Lasse的评论是正确的: HashSet的合同要求你不要这样做,所以当你这样做时要问该怎么做是不可能的。如果你这样做会伤害,停止这样做如果突变会导致其哈希值在集合中发生变化,则不得将可变对象放入哈希集。你是自己制作的一个裂缝。

为了摆脱那个裂缝,你可以:

  • 当对象在哈希集中时停止变异。在变异之前将它们移除,稍后将它们放回去。
  • 修复对象上的相等和散列的实现,使其在突变中保持一致。
  • 创建哈希集时,提供自定义哈希/相等算法,该算法在对象发生变异时不会改变其意见。
  • 实现您自己的“set”类,该类在此方案中具有您喜欢的任何行为。这非常困难,所以要小心。 (首先要创建这种限制的原因!)

答案 1 :(得分:3)

除了重新创建HashSet<>之外别无他法。遗憾的是HashSet<>构造函数有一个优化,所以如果它是从另一个HashSet<>创建的,它会复制哈希代码...所以我们可以作弊:

hs = new HashSet<Test>(hs.Skip(0), eq);

hs.Skip(0)IEnumerable<>,而不是HashSet<>。这会使HashSet<>支票失败。

请注意,无法保证将来Skip()在0的情况下不会实现短路,例如:

if (count == 0)
{
    return enu;
}
else
{
    return count elements;
}

<击>

(见Lippert的评论,虚假问题)

执行此操作的“手动”方法是:

var hs2 = new HashSet<Test>(eq);
foreach (var value in hs)
{
    hs2.Add(value);
}
hs = hs2;

因此,“手动”枚举并读取。

答案 2 :(得分:2)

如您所见,HashSet在修改对象时会影响其哈希代码或与其他对象的相等性,从而不会处理可变对象。只需将其删除并重新添加即可:

hs.Remove(b);
b.N = 1;
hs.Add(b);