The VS2005 documentation Guidelines for Overloading Equals() and Operator == (C# Programming Guide) states in part
Overriding operator == in non-immutable types is not recommended.
The newer .NET Framework 4 documentation Guidelines for Implementing Equals and the Equality Operator (==) omits that statement, although one post in Community Content repeats the assertion and references the older documentation.
It seems that it is reasonable to override Equals() at least for some trivial mutable classes, such as
public class ImaginaryNumber
{
public double RealPart { get; set; }
public double ImaginaryPart { get; set; }
}
In math, two imaginary numbers that have the same real part and the same imaginary part are in fact equal at the point in time that equality is tested. It is incorrect to assert that they are not equal, which would happen if separate objects with the same RealPart and ImaginaryPart were Equals() not overridden.
On the other hand, if one overrides Equals() one should also override GetHashCode(). If an ImaginaryNumber that overrides Equals() and GetHashCode() is placed in a HashSet, and a mutable instance changes its value, that object would no longer be found in the HashSet.
Was MSDN incorrect to remove the guideline about not overriding Equals()
and operator==
for non-immutable types?
Is it reasonable to override Equals() for mutable types where "in the real world" equivalence of all properties means that the objects themselves are equal (as with ImaginaryNumber
)?
If it is reasonable, how does one best deal with potential mutability while an object instance is participating in a HashSet or something else that relies on GetHashCode() not changing?
UPDATE
Just came across this in MSDN
Typically, you implement value equality when objects of the type are expected to be added to a collection of some sort, or when their primary purpose is to store a set of fields or properties. You can base your definition of value equality on a comparison of all the fields and properties in the type, or you can base the definition on a subset. But in either case, and in both classes and structs, your implementation should follow the five guarantees of equivalence:
ImaginaryNumber
to be a mutable type. – Diseasedint
,long
,double
etc... are not mutable. A5
is a5
- you can't change what it is. – Starnesint i = 0; i = 42;
will not mutate the value ofi
? – Mackayi
is not mutated. Instead,i
is assigned a new value (which may be based on the previous value ofi
) – Diseasedi
is a variable.0
is a literal - it is immutable. When you reassign42
to the variablei
, the variable now has a different value.42
and0
have not changed. – Starnesi
is changed, but theInt32
0
is still0
. – PauperismImaginaryNumber
type should be modelled as an immutable type. – Starnes