【发布时间】:2021-04-26 02:42:51
【问题描述】:
我有两个这样的课程:
class Base : IEquatable<Base>
{
int A;
public Base(int a)
{
A = a;
}
public bool Equals([AllowNull] Base other)
{
if (other is null)
return false;
else
return (A == other.A);
}
public override bool Equals(object other)
{
return Equals(other as Base);
}
public static bool operator==(Base one, Base two)
{
return (one is null) ? (two is null) : one.Equals(two);
}
public static bool operator !=(Base one, Base two)
{
return !(one == two);
}
public override int GetHashCode()
{
return HashCode.Combine(A);
}
}
class Derived : Base, IEquatable<Derived>
{
int B;
public Derived(int a, int b)
: base(a)
{
B = b;
}
public bool Equals([AllowNull] Derived other)
{
if (other is null)
return false;
else
return (A == other.A) && (B == other.B);
}
public override bool Equals(object other)
{
return Equals(other as Derived);
}
public static bool operator==(Derived one, Derived two)
{
return (one is null) ? (two is null) : one.Equals(two);
}
public static bool operator !=(Derived one, Derived two)
{
return !(one == two);
}
public override int GetHashCode()
{
return HashCode.Combine(A, B);
}
}
如果我有这样的字典:
Dictionary<Base, string> Dict = new Dictionary<Base, string>();
我想确保字典正确分隔所有不同的值:
Dict[new Base(1)] = "one";
Dict[new Derived(1, 2)] = "one, two";
Dict[new Derived(1, 3)] = "one, three";
Assert.AreEqual(3, Dict.Count);
Derived der13again = new Derived(1, 3);
Assert.IsTrue(Dict.ContainsKey(der13again));
// Notice this Assert is why I had to override IEquatable<> so that the
// keys are compared by value, not by reference
我需要确保它适用于所有情况,即使是存在哈希冲突的罕见情况。为了强制哈希冲突,我编辑了 Derived.GetHashCode() 以仅返回 HashCode.Combine(A)。当我这样做并添加一些调试时,第一个 Assert 失败(字典仅包含 1 个条目),我得到以下输出:
// Adding [Base 1] to dict
Base.GetHashCode() returning -1259686161 [this=[Base 1]]
dict has 1 members
// So far so good
// Step 2: Adding [Derived 1, 2] to dict
Derived.GetHashCode() returning -1259686161 [this=[Derived 1, 2]]
// Dictionary notices the hash collision and wonders if it's actually
// the same key value, so it calls Equals() to check if old key == new key
// BUT USES BASE.EQUALS
Base.Equals(Base other=[Derived 1, 2])
// Dictionary decides the two keys are identical, so updates the value:
dict has 1 members
dict[b]=one, two
dict[d1]=one, two
// Adding [Derived 1, 3] to dict
Derived.GetHashCode() returning -1259686161 [this=[Derived 1, 3]]
// Again, hash collision, Dictionary wonders if it's the same key
Base.Equals(Base other=[Derived 1, 3])
// Decides it's the same key so just updates the value
dict has 1 members
dict[b]=one, three
dict[d1]=one, three
dict[d2]=one, three
编辑:根据 Jon Skeet 的评论,我更新了 Base.Equals() 函数,如下所示:
public bool Equals([AllowNull] Base other)
{
if (other is null)
return false;
else if (GetType() != other.GetType())
return false;
else
return (A == other.A);
}
这有助于现在我的字典中有两个对象,但仍然不是 3(字典仍然使用 Base.Equals 比较两个派生值,因此发现它们相等)
如何让 Dictionary 正确处理派生类?
【问题讨论】:
-
你到底想用那本词典实现什么?
-
这是一个虚构的例子,展示了问题的相关部分。实际问题是 (1) 太长而无法包含在 stackoverflow 帖子中,以及 (2) 专有。
-
最好了解一下哈希表是如何处理冲突的。这不是你想象的那样。
-
你说的很对,我已经调整了我原来的帖子,不暗示 Equals 是正常哈希冲突过程的一部分。
-
我相信你的
Equals实现基本上被破坏了——因为“x.Equals(y)返回与y.Equals(x)”相同的值的requirement 被违反了。base.Equals(der1)将返回 true,而der1.Equals(base)将返回 false。一旦你有一个一致的平等实现,一切都应该没问题。
标签: c# dictionary derived-class