С двумя неизменяемыми классами 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)
в этой реализации выполнит некоторые операции по упаковке/распаковке, но это того стоит.
Мои вопросы -
Во-первых, это правильно? мои (тестирование), кажется, предполагают, что это так, но из-за того, что С# настолько сложен в равенстве, я просто не уверен больше... есть ли случаи, когда это не так?
и второе - это хорошо? Есть ли более чистые способы достичь этого?