It's unfortunate that while there two different questions that an equality-test method could meaningfully ask of any pair of object references X
and Y
, there's only one Equals
method and one GetHashCode
method.
Assuming X
and Y
are of the same type(*), will all members of X
always behave the same as corresponding methods of Y
? Two references to different arrays would be reported as unequal under this definition even if they contain matching elements, since even if their elements are the same at one moment in time, that may not always be true.
Assuming X
and Y
are of the same type(*), would simultaneously replacing all references to object X
with references to object Y
, and vice versa, affect any members of either other than an identity-based GetHashCode
function? References to two distinct arrays whose elements match would be reported as equal under this definition.
(*) In general, objects of different types should report unequal. There might be some cases where one could argue that objects of different private classes which are inherited from the same public class should be considered equal, if all of the code which has access to the private classes only stores reference in the matching public type, but that would be at most a pretty narrow exception.
Some situations require asking the first question, and some require asking the second; the default Object
implementation of Equals
and GetHashCode
answer the first, while the default ValueType
implementations answer the second. Unfortunately, the selection of which comparison method is appropriate for a given reference is a function of how the reference is used, rather than a function of the referred-to instance's type. If two objects hold references to collections which they will neither mutate nor expose to code that might do so, for the intention of encapsulating the contents thereof, equality of the objects holding those references should depend upon the contents of the collections, rather than their identity.
It looks as though code is sometimes using instances of type PlayList
in ways where the first question is more appropriate, and sometimes in ways where the second would be more appropriate. While that may be workable, I think it might be better to have a common data-holder object which can be wrapped if necessary by an object whose equality-check method would be suitable for one use or the other (e.g. have a PlaylistData
object which can be wrapped by either a MutablePlaylist
or an ImmutablePlaylist
). The wrapper classes could have InvalidateAndMakeImmutable
or InvalidateAndMakeMutable
methods which would invalidate the wrapper and return a new wrapper around the object (using wrappers would ensure that the system would know whether a given Playlist
reference could be exposed to code that might mutate it).