在这种情况下,编辑一个成员以使其与另一个成员相等,强制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中,清除自身,然后从所述列表重新填充。
这是真的有必要还是有一些更简单的方法来做到这一点?
答案 0 :(得分:9)
Lasse的评论是正确的: HashSet的合同要求你不要这样做,所以当你这样做时要问该怎么做是不可能的。如果你这样做会伤害,停止这样做。 如果突变会导致其哈希值在集合中发生变化,则不得将可变对象放入哈希集。你是自己制作的一个裂缝。
为了摆脱那个裂缝,你可以:
答案 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);