Without doing anything special for a reference type, Equals()
would mean reference equality (i.e. same object). If I choose to override Equals()
for a reference type, should it always mean that the values of the two objects are equivalent?
Consider this mutable Person
class:
class Person
{
readonly int Id;
string FirstName { get; set; }
string LastName { get; set; }
string Address { get; set; }
// ...
}
Two objects that represent the exact same person will always have the same Id
, but the other fields might be different over time (i.e. before/after an address change).
For this object Equals could be defined to mean different things:
Ids
are equal (two objects representing the same person but with different addresses would return true)Question: Which (if any) of these is preferable for this class? (Or perhaps the question should be, "how would most clients of this class expect Equals() to behave?")
Notes:
Hashset
or Dictionary
Using Identity Equality makes the relationship between Equals and the =
operator strange (i.e. after a check of two Person objects (p1 and p2) returns true for Equals()
, you might still want to update your reference to point to the "newer" Person object since it is not value equivalent). For example, the following code reads strange--seems like it does nothing, but it is actually removing p1 and adding p2:
HashSet<Person> people = new HashSet<Person>();
people.Add(p1);
// ... p2 is an new object that has the same Id as p1 but different Address
people.Remove(p2);
people.Add(p2);
Related Questions:
Fundamentally, if class-type fields (or variables, array slots, etc.) X
and Y
each hold a reference to a class object, there are two logical questions that (Object)X.Equals(Y)
can answer:
Note that if X
and Y
refer to objects of different types, neither function may legitimately return true unless both classes know that there cannot be any storage locations holding a reference to one which could not also hold a reference to the other [e.g. because both types are private classes derived from a common base, and neither is ever stored in any storage location (other than this
) whose type can't hold references to both].
The default Object.Equals
method answers the first question; ValueType.Equals
answers the second. The first question is generally the appropriate one to ask of object instances whose observable state may be mutated; the second is appropriate to ask of object instances whose observable state will not be mutated even if their types would allow it. If X
and Y
each hold a reference to a distinct int[1]
, and both arrays hold 23 in their first element, the first equality relation should define them as distinct [copying X
to Y
would alter the behavior of X[0]
if Y[0]
were modified], but the second should regard them as equivalent (swapping all references to the targets of X
and Y
wouldn't affect anything). Note that if the arrays held different values, the second test should regard the arrays as distinct, since swapping the objects would mean X[0]
would now report the value that Y[0]
used to report).
There's a pretty strong convention that mutable types (other than System.ValueType
and its descendants) should override Object.Equals
to implement the first type of equivalence relation; since it's impossible for System.ValueType
or its descendants to implement the first relation, they generally implement the second. Unfortunately, there's no standard convention by which objects which override Object.Equals()
for the first kind of relation should expose a method which tests for the second, even though an equivalence relation could be defined which allowed comparison between any two objects of any arbitrary type. The second relation would be useful in the standard pattern wherein an immutable class Imm
holds a private reference to a mutable type Mut
but doesn't expose that object to any code that could actually mutate it [making the instance immutable]. In such a case, there's no way for class Mut
to know that an instance will never be written, but it would be helpful to have a standard means by which two instances of Imm
could ask the Mut
s to which they hold references whether they would be equivalent if the holders of the references never mutated them. Note that the equivalence relation defined above makes no reference to mutation, nor to any particular means which Imm
must use to ensure that an instance won't be mutated, but its meaning is well-defined in any case. The object which holds a reference to Mut
should know whether that reference encapsulates identity, mutable state, or immutable state, and should thus be able to implement its own equality relation suitably.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With