ICloneable`1
is implemented by dispatching to non-polymorphic copy constructors which, in turn, call the appropriate base class constructor.
This implements semantically correct deep copying.
In order to get the correct types, the CRTP interface is (a) inherited from the base class, and (b) reimplemented explicitly.
For IEquatable`1
, we can just overload (note: not override!) the Equals
method in the subclasses.
In addition, we declare the interface — purely for documentation purposes.
Finally, we could additionally override the base class Equals
method and do either of the following:
- Bail out:
public override bool Equals(Pet other) => throw new InvalidCastException();
- Implement polymorphic
Equals
; this would require an upcast, though.
In real code, I am tempted to go with (1) for most situations.
But for some situations (2) is needed (e.g. for some operations on expression trees).
However, I tend to implement those hierarchies completely differently anyway (namely, with a sum type; e.g. std::variant
in C++).
The solution then is to use visitors instead of subclass polymorphism.
As it stands, comparing cats and dogs (or apples and oranges) silently does the wrong thing here.
-
I haven’t used C# in ages. I think the co-/contravariance of the interfaces is correct but I can’t be bothered to check.
-
This should really be
abstract
to signal to implementers of the derived classes that it needs to be overridden. C++ allows this (pure virtual functions can have a default implementation). -
This is purely a convenience method to help me avoid having to write:
ICloneable<Cat> catToClone = cat; Cat cc2 = catToClone.Clone();
It’s a shame that C# doesn’t allow the following, more expressive signature, which would work in C++:
static T As<T, U>(U obj) where U : T => obj; // Usage same as before, i.e. only specifying the first generic type argument.
-
If C# allowed overload resolution based on return type, the following would work, too (again, C++ can do this — with some trickery):
Cat cc3 = cat.Clone();