Anyone have any opinions on whether or not IEquatable<T>
or IComparable<T>
should generally require that T
is sealed
(if it's a class
)?
This question occurred to me since I'm writing a set of base classes intended to aid in the implementation of immutable classes. Part of the functionality which the base class is intended to provide is automatic implementation of equality comparisons (using the class's fields together with attributes which can be applied to fields to control equality comparisons). It should be pretty nice when I'm finished - I'm using expression trees to dynamically create a compiled comparison function for each T
, so the comparison function should be very close to the performance of a regular equality comparison function. (I'm using an immutable dictionary keyed on System.Type
and double check locking to store the generated comparison functions in a manner that's reasonably performant)
One thing that has cropped up though, is what functions to use to check equality of the member fields. My initial intention was to check if each member field's type (which I'll call X
) implements IEquatable<X>
. However, after some thought, I don't think this is safe to use unless X
is sealed
. The reason being that if X
is not sealed
, I can't know for sure if X
is appropriately delegating equality checks to a virtual method on X
, thereby allowing a subtype to override the equality comparison.
This then brings up a more general question - if a type is not sealed, should it really implement these interfaces AT ALL?? I would think not, since I would argue that the interfaces contract is to compare between two X
types, not two types which may or may not be X
(though they must of course be X
or a subtype).
What do you guys think? Should IEquatable<T>
and IComparable<T>
be avoided for unsealed classes? (Also makes me wonder if there is an fxcop rule for this)
My current thought is to have my generated comparison function only use IEquatable<T>
on member fields whose T
is sealed
, and instead to use the virtual Object.Equals(Object obj)
if T
is unsealed even if T
implements IEquatable<T>
, since the field could potentially store subtypes of T
and I doubt most implementations of IEquatable<T>
are designed appropriately for inheritance.
Most
Equals
implementations I've seen check the types of the objects being compared, if they aren't the same then the method returns false.This neatly avoids the problem of a sub-type being compared against it's parent type, thereby negating the need for sealing a class.
An obvious example of this would be trying to compare a 2D point (A) with a 3D point (B): for a 2D the x and y values of a 3D point might be equal, but for a 3D point, the z value will most likely be different.
This means that
A == B
would be true, butB == A
would be false. Most people like theEquals
operators to be commutative, to checking types is clearly a good idea in this case.But what if you subclass and you don't add any new properties? Well, that's a bit harder to answer, and possibly depends on your situation.
I would generally recommend against implementing IEquatable<T> on any non-sealed class, or implementing non-generic IComparable on most, but the same cannot be said for IComparable<T>. Two reasons:
Implementation of non-generic IComparable in inheritable classes is perhaps more questionable than implementation of IComparable<T>. Probably the best thing to do is allow a base-class to implement it if it's not expected that any child class will need some other ordering, but for child classes not to reimplement or override parent-class implementations.
I have stumbled over this topic today when reading
https://blog.mischel.com/2013/01/05/inheritance-and-iequatable-do-not-mix/
and I agree, that there are reasons not to implement
IEquatable<T>
, because chances exist that it will be done in a wrong way.However, after reading the linked article I tested my own implementation which I use on various non-sealed, inherited classes, and I found that it's working correctly.
When implementing
IEquatable<T>
, I referred to this article:http://www.loganfranken.com/blog/687/overriding-equals-in-c-part-1/
It gives a pretty good explanation what code to use in
Equals()
. It does not address inheritance though, so I tuned it myself. Here's the result.And to answer the original question:
I don't say that it should be implemented on non-sealed classes, but I say that it definitely could be implemented without problems.
Test:
I've been thinking about this question for a bit and after a bit of consideration I agree that implementing
IEquatable<T>
andIComparable<T>
should only be done on sealed types.I went back and forth for a bit but then I thought of the following test. Under what circumstances should the following ever return false? IMHO, 2 objects are either equal or they are not.
The result of
IEquatable<T>
on a given object should have the same behavior asObject.Equals
assuming the comparer is of the equivalent type. ImplementingIEquatable<T>
twice in an object hierarchy allows for, and implies, there are 2 different ways of expressing equality in your system. It's easy to contrive any number of scenarios whereIEquatable<T>
andObject.Equals
would differ since there are multipleIEquatable<T>
implementations but only a singleObject.Equals
. Hence the above would fail and create a bit of confusion in your code.Some people may argue that implementing
IEquatable<T>
at a higher point in the object hierarchy is valid because you want to compare a subset of the objects properties. In that case you should favor anIEqualityComparer<T>
which is specifically designed to compare those properties.