有两个不可变的类Base和Derived(派生自Base),我想定义相等性,使得:
相等性始终是多态的 - 也就是说,
((Base)derived1).Equals((Base)derived2)
将调用Derived.Equals
操作符
==
和!=
将调用Equals
而不是ReferenceEquals
(值相等性)
我的做法:
class Base: IEquatable<Base> {
public readonly ImmutableType1 X;
readonly ImmutableType2 Y;
public Base(ImmutableType1 X, ImmutableType2 Y) {
this.X = X;
this.Y = Y;
}
public override bool Equals(object obj) {
if (object.ReferenceEquals(this, obj)) return true;
if (obj is null || obj.GetType()!=this.GetType()) return false;
return obj is Base o
&& X.Equals(o.X) && Y.Equals(o.Y);
}
public override int GetHashCode() => HashCode.Combine(X, Y);
// boilerplate
public bool Equals(Base o) => object.Equals(this, o);
public static bool operator ==(Base o1, Base o2) => object.Equals(o1, o2);
public static bool operator !=(Base o1, Base o2) => !object.Equals(o1, o2); }
所有内容最终都会进入Equals(object)
,该方法始终是多态的,因此两个目标都可以实现。
然后我像这样派生:
class Derived : Base, IEquatable<Derived> {
public readonly ImmutableType3 Z;
readonly ImmutableType4 K;
public Derived(ImmutableType1 X, ImmutableType2 Y, ImmutableType3 Z, ImmutableType4 K) : base(X, Y) {
this.Z = Z;
this.K = K;
}
public override bool Equals(object obj) {
if (object.ReferenceEquals(this, obj)) return true;
if (obj is null || obj.GetType()!=this.GetType()) return false;
return obj is Derived o
&& base.Equals(obj) /* ! */
&& Z.Equals(o.Z) && K.Equals(o.K);
}
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Z, K);
// boilerplate
public bool Equals(Derived o) => object.Equals(this, o);
}
基本相同,唯一的不同之处在于一个小技巧 - 调用base.Equals
时,我调用了base.Equals(object)
而不是base.Equals(Derived)
(这会导致无限递归)。
此实现中的Equals(C)
也会进行一些装箱/拆箱操作,但对于我来说这是值得的。
我的问题是 -
首先,这样做是否正确?我的测试似乎表明正确,但由于C#在相等性方面非常困难,我已经不确定了...是否存在任何情况下这种方法是错误的?
其次,这样做好吗?是否有更好、更清晰的方法可以实现这一点?
Base.Equals(Object)
可以接受Derived
的实例,如果X
和Y
相等,那么这两个实例将是相等的,而完全忽略了Derived
更多的内容。也就是说,new Base(1, 2).Equals(new Derived(1, 2, 3, 4))
会返回true
,这让我很难理解为什么要称之为"多态"。如果一个对象只能与同一类型的另一个实例相等,但是可以从一个共同的基类进行比较,这感觉更加正确,并且可以极大地简化问题。 - madreflectionX
、Y
、Z
和K
变量是可变的,这对于覆盖GetHashCode
是不好的。哈希码应该永远不会改变。 - Enigmativityreadonly
并不等同于"immutable"(不可变)。如果您使用来自readonly
变量的哈希码,并且它们反过来又从其可变属性计算出哈希码,那么您仍然处于同样的位置。您需要确保哈希码在整个对象模型中都不会发生变化。 - Enigmativitya==b
与b==a
一致,a==a
始终为真,且支持传递性;如果a==b
和b==c
为真,则a==c
必须为真。很多相等性的实现都无法满足这些标准,然后糟糕的事情就会发生。 - Eric Lippert