Say for a Point2 class, and the following Equals:
public override bool Equals ( object obj ) public bool Equals ( Point2 obj )
This is the one that is shown in the Effective C# 3:
public override bool Equals ( object obj ) { // STEP 1: Check for null if ( obj == null ) { return false; } // STEP 3: equivalent data types if ( this.GetType ( ) != obj.GetType ( ) ) { return false; } return Equals ( ( Point2 ) obj ); } public bool Equals ( Point2 obj ) { // STEP 1: Check for null if nullable (e.g., a reference type) if ( obj == null ) { return false; } // STEP 2: Check for ReferenceEquals if this is a reference type if ( ReferenceEquals ( this, obj ) ) { return true; } // STEP 4: Possibly check for equivalent hash codes if ( this.GetHashCode ( ) != obj.GetHashCode ( ) ) { return false; } // STEP 5: Check base.Equals if base overrides Equals() System.Diagnostics.Debug.Assert ( base.GetType ( ) != typeof ( object ) ); if ( !base.Equals ( obj ) ) { return false; } // STEP 6: Compare identifying fields for equality. return ( ( this.X.Equals ( obj.X ) ) && ( this.Y.Equals ( obj.Y ) ) ); }
There is a whole set of guidelines on MSDN as well. You should read them well, it is both tricky and important.
A few points I found most helpful:
Value Types don’t have Identity, so in a
struct Pointyou will usually do a member by member compare.Reference Types usually do have identity, and therefore the Equals test usually stops at ReferenceEquals (the default, no need to override). But there are exceptions, like string and your
class Point2, where an object has no useful identity and then you override the Equality members to provide your own semantics. In that situation, follow the guidelines to get through the null and other-type cases first.And there are good reasons to keep
GethashCode()andoperator==in sync as well.