How to best implement Equals for custom types?

Viewed 63121

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 ) ) );
}
11 Answers

In the one that takes an obj, if the type of obj is Point2, call the type specific Equals. Inside the type specific Equals, make sure that all the members have the same value.

public override bool Equals ( object obj )
{
   return Equals(obj as Point2);
}

public bool Equals ( Point2 obj )
{
   return obj != null && obj.X == this.X && obj.Y == this.Y ... 
   // Or whatever you think qualifies as the objects being equal.
}

You probably ought to override GetHashCode as well to make sure that objects that are "equal" have the same hash code.

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 Point you 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() and operator== in sync as well.

Using C# 7 and the is type varname pattern matching syntax provides for a clean Equals(object) that deals with null and type checking using either of the below approaches:

// using strongly-typed overload of Equals
public override bool Equals(object obj) => (obj is Point2 other) && Equals(other);

public bool Equals(Point2 other);
// using the == operator (requires != to also be defined)
public override bool Equals(object obj) => (obj is Point2 other) && this == other;

public static bool operator ==(Point2 lhs, Point2 rhs);
public static bool operator !=(Point2 lhs, Point2 rhs);

(Obviously you need to provide implementation of the relevant method stubs.)

  • Define what the identity means.. if reference identity then the default inherited equals will work.
  • If a value type (and thus value identity) you need to define.
  • If a class type, but has value semantics then define.

Likely you want to both override Equals(object) and define Equals(MyType) because the latter avoids boxing. And override the equality operator.

.NET Framework Guidelines book (2nd ed) has more coverage.

The simple and best way to override Equals looks like:

public class Person
    {
        public int Age { get; set; }
        public string Name { get; set; }

        public override bool Equals(object other)
        {
            Person otherItem = other as Person;

            if (otherItem == null)
                return false;

            return Age == otherItem.Age && Name == otherItem.Name;
        }
        public override int GetHashCode()
        {
            int hash = 13;
            hash = (hash * 7) + Age.GetHashCode();
            hash = (hash * 7) + Name.GetHashCode();
            return hash;
        }
    }

Override the GetHashCode method to allow a type to work correctly in a hash table.

Lie Daniel L said,

public override bool Equals(object obj) {
    Point2 point = obj as Point2; // Point2? if Point2 is a struct
    return point != null && this.Equals(point);
}

public bool Equals(Point2 point) {
    ...
}
public override bool Equals ( object obj )
{
   // struct
   return obj  is Point2 && Equals (  ( Point2 ) value );
   // class
   //return Equals ( obj as Point2 );
}

public bool Equals ( Point2 obj )

I've found this to work for me - I let GetHashCode() to do all the heavy lifting.

After that, any two objects are equal if and only if their hash codes are equal.

    public override int GetHashCode() => <-- IMPLEMENT THIS -->
    public virtual bool Equals(T other) => other?.GetHashCode() == GetHashCode();
    public override bool Equals(object other) => Equals(other as T);
    public static bool operator ==(T item1, T item2) => item1?.GetHashCode() == item2?.GetHashCode();
    public static bool operator !=(T item1, Titem2) => !(item1 == item2);

Example of a Wrapper class which adds some info to a sealed class of type S.

public class Wrapper<S>
{
    public S obj;

    // Handle obj being null
    public override int GetHashCode() => obj?.GetHashCode() ?? 0;

    // The rest are the same as above
    public virtual bool Equals(Wrapper<S> other) => other?.GetHashCode() == GetHashCode();
    public override bool Equals(object other) => Equals(other as Wrapper<S>);
    public static bool operator ==(Wrapper<S> item1, Wrapper<S> item2) => item1?.GetHashCode() == item2?.GetHashCode();
    public static bool operator !=(Wrapper<S> item1, Wrapper<S> item2) => !(item1 == item2);
}
Related