For equality comparison of a type "T", overload these methods:
int GetHashCode() //Overrides Object.GetHashCode
bool Equals(object other) //Overrides Object.Equals; would correspond to IEquatable, if such an interface existed
bool Equals(T other) //Implements IEquatable<T>; do this for each T you want to compare to
static bool operator ==(T x, T y)
static bool operator !=(T x, T y)
Your type-specific comparison code should be done in one place: the type-safe IEquatable<T>
interface method Equals(T other)
.
If you're comparing to another type (T2), implement IEquatable<T2>
as well, and put the field comparison code for that type in Equals(T2 other).
All overloaded methods and operators should forward the equality comparison task to the main type-safe Equals(T other) instance method, such that an clean dependency hierarchy is maintained and stricter guarantees are introduced at each level to eliminate redundancy and unessential complexity.
bool Equals(object other)
{
if (other is T) //replicate this for each IEquatable<T2>, IEquatable<T3>, etc. you may implement
return Equals( (T)other) ); //forward to IEquatable<T> implementation
return false; //other is null or cannot be compared to this instance; therefore it is not equal
}
bool Equals(T other)
{
if ((object)other == null) //cast to object for reference equality comparison, or use object.ReferenceEquals
return false;
//if ((object)other == this) //possible performance boost, ONLY if object instance is frequently compared to itself! otherwise it's just an extra useless check
//return true;
return field1.Equals( other.field1 ) &&
field2.Equals( other.field2 ); //compare type fields to determine equality
}
public static bool operator ==( T x, T y )
{
if ((object)x != null) //cast to object for reference equality comparison, or use object.ReferenceEquals
return x.Equals( y ); //forward to type-safe Equals on non-null instance x
if ((object)y != null)
return false; //x was null, y is not null
return true; //both null
}
public static bool operator !=( T x, T y )
{
if ((object)x != null)
return !x.Equals( y ); //forward to type-safe Equals on non-null instance x
if ((object)y != null)
return true; //x was null, y is not null
return false; //both null
}
Discussion:
The preceding implementation centralizes the type-specific (i.e. field equality) comparison to the end of the IEquatable<T>
implementation for the type.
The ==
and !=
operators have a parallel but opposite implementation. I prefer this over having one reference the other, such that there is an extra method call for the dependent one. If the !=
operator is simply going to call the ==
operator, rather than offer an equally performing operator, then you may as well just use !(obj1 == obj2)
and avoid the extra method call.
The comparison-to-self is left out from the equals operator and the IEquatable<T>
implementations, because it can introduce 1. unnecessary overhead in some cases, and/or 2. inconsistent performance depending on how often an instance is compared to itself vs other instances.
An alternative I don't like, but should mention, is to reverse this setup, centralizing the type-specific equality code in the equality operator instead and have the Equals methods depend on that. One could then use the shortcut of ReferenceEquals(obj1,obj2)
to check for reference equality and null equality simultaneously as Philip mentioned in an earlier post, but that idea is misleading. It seems like you're killing two birds with one stone, but your actually creating more work -- after determining the objects are neither both null nor the same instance, you will then, in addition, STILL have to on to check whether each instance is null. In my implementation, you check for any single instance being null exactly once. By the time the Equals instance method is called, it's already ruled out that the first object being compared is null, so all that's left to do is check whether the other is null. So after at most two comparisons, we jump directly into the field checking, no matter which method we use (Equals(object),Equals(T),==,!=
). Also, as I mentioned, if you really are comparing and object to itself the majority of the time, then you could add that check in the Equals method just before diving into the field comparisons. The point in adding it last is that you can still maintain the flow/dependency hierarchy without introducing a redundant/useless check at every level.