#417 – Provide a Type-Specific Equals Method for Value Equality

When you are implementing value equality in a type, you typically override the Equals method that is defined in System.Object.  It has the following signature:

        public override bool Equals(object obj)

You should also add a type-specific Equals method.  For completeness, you can indicate that your class implements IEquatable<T>, which includes the type-specific Equals method.

    public class Dog : IEquatable<Dog>

Below is a complete example, showing us the override of System.Object.Equals, as well as the type-specific Equals method.  Note that the generic Equals method calls the type-specific version.

        // System.Object.Equals
        public override bool Equals(object obj)
        {
            return this.Equals(obj as Dog);
        }

        // IEquatable<Dog>.Equals
        public bool Equals(Dog d)
        {
            if (d == null)
                return false;

            return (Name == d.Name) && (Age == d.Age);
        }
Advertisement

#413 – Check for Reference Equality Using Object.ReferenceEquals

You might encounter types that override both the == operator and the Equals method to check for value equality.

For example, a PersonHeight value type might behave this way.  Both checks return true in the example below.

            PersonHeight ph1 = new PersonHeight(5, 10);
            PersonHeight ph2 = new PersonHeight(5, 10);

            // Both == operator and Equals method are checking
            // for value equality
            bool check = ph1 == ph2;    // true
            check = ph1.Equals(ph2);    // true

If you want to do a reference equality check, you can always use the Object.ReferenceEquals method. This method cannot be overridden and always does a reference equality check–i.e. compares two object references to see if they refer to the same object.

            // Check for reference equality
            check = ReferenceEquals(ph1, ph2);      // false

#411 – Overriding the Equals Method for a Value Type

A user-defined struct inherits from System.ValueType implicitly.

System.ValueType includes a default implementation of the Equals method that compares the individual fields of two instances of the type, to determine if the instances are equivalent.  It uses reflection to determine which fields to compare and compares both public and private fields.  (This includes the values of auto-implemented properties, which use an automatically generated private backing field).

You should generally override the Equals method in a user-defined struct, comparing all of the fields directly.  This is typically faster than the default implementation, since your method does not need to use reflection.

If you are also overloading the == operator, you can define Equals in terms of the operator.

        public override bool Equals(object obj)
        {
            return ((obj is PersonHeight) && ((PersonHeight)obj == this));
        }

        public static bool operator ==(PersonHeight ph1, PersonHeight ph2)
        {
            return (ph1.Feet == ph2.Feet) && (ph1.Inches == ph2.Inches);
        }

#409 – Example of Overloading the == Operator

Here’s a full example that shows how to overload the == operator for a reference type.

    public class PersonHeight
    {
        public int Feet { get; set; }
        public int Inches { get; set; }

        // Constructor goes here

        public override bool Equals(object obj)
        {
            return ((obj is PersonHeight) && ((PersonHeight)obj == this));
        }

        public override int GetHashCode()
        {
            return Feet.GetHashCode() ^ Inches.GetHashCode();
        }

        public static bool operator ==(PersonHeight ph1, PersonHeight ph2)
        {
            if (ReferenceEquals(ph1, ph2))
                return true;

            if (((object)ph1 == null) || ((object)ph2 == null))
                return false;

            return (ph1.Feet == ph2.Feet) && (ph1.Inches == ph2.Inches);
        }

        public static bool operator !=(PersonHeight ph1, PersonHeight ph2)
        {
            return !(ph1 == ph2);
        }

        // Also overload < and > operators
    }

Test cases:

            PersonHeight ph1 = new PersonHeight(5, 10);
            PersonHeight ph2 = new PersonHeight(5, 10);
            PersonHeight ph3 = null;

            bool check = ph1.Equals(null);
            check = ph1.Equals("NO!");
            check = ph1.Equals(ph2);
            check = PersonHeight.Equals(ph1, ph2);

            check = (ph1 == null);
            check = (ph3 == null);
            check = (ph1 == ph2);

#406 – Overriding the Equals Method

You can override the Equals method in a custom type to implement value equality for two instances of the type.

Guidelines to follow when overriding Equals include:

  • x.Equals(x) should return true
  • x.Equals(y) should return the same value as y.Equals(x)
  • x.Equals(y) and y.Equals(z) implies that x.Equals(z)
  • Repeated calls to x.Equals(y) return the same result, for the same values of x and y
  • x.Equals(null) returns false

Additionally:

  • When you override Equals, you should also override GetHashCode

Here’s the implementation of Equals for the Dog class:

        // Are two Dogs equivalent?
        public override bool Equals(object obj)
        {
            // Can't be null
            if (obj == null)
                return false;

            // Must be a Dog
            if (obj is Dog)
            {
                // Compare the dogs
                Dog d2 = (Dog)obj;
                return (Name == d2.Name) && (Age == d2.Age);
            }
            else
                return false;
        }

Here’s the override of GetHashCode.

        public override int GetHashCode()
        {
            return Name.GetHashCode() ^ Age;
        }

#405 – Equals Method for Equivalence, == Operator for Identity

You can check for equality between two objects by calling the Equals method or by using the == operator.  There are some differences between how the methods work, but both Equals and == can be overloaded in a user-defined type.

You can therefore change the behavior of one of these methods, or both.  The question is–how should these two behaviors work for a custom class?

Roughly speaking, the desired behavior, from a client’s point of view is:

  • Use Equals method to determine value equality, or equivalence–do the two objects represent the same thing?
  • Use == operator to determine reference equality, or identity–do the two references point to exactly the same object?

This argues for overloading Equals for a user-defined reference type, but not overloading the == operator.

This would result in the following behavior:

            Dog d1 = new Dog("Lassie", 7);
            Dog d2 = new Dog("Lassie", 7);

            bool bValueEquality = d1.Equals(d2);    // true
            bool bRefEquality = d1 == d2;    // false

#404 – Equals Method vs. == Operator for Value Types

In C#, you can compare two objects for equality by using the Equality operator (==) or by calling the Equals method.  For built-in value types, the default behavior checks for value equality, or equivalence.

            int i1 = 42;
            int i2 = 42;

            bool check1 = i1 == i2;      // true
            bool check2 = i1.Equals(i2);  // true
            bool check3 = int.Equals(i2, i2);  // true

For the predefined value types (e.g. int, float):

  • The Equals method performs a value equality check
  • The == operator resolves to the CIL ceq instruction, which does a strict identity check

For user-defined struct types:

  • The Equals method implementation performs a value equality check by comparing each field of the struct (using reflection).  You can (and should) override Equals to provide an implementation specific to the struct.
  • The == operator is undefined, unless you override it in the struct.

#403 – Equals Method vs. == Operator for Reference Types

In C#, you can compare two objects for equality by using the Equality operator (==) or by calling the Equals method.  In both cases, the default behavior for reference types is to check for reference equality, or identity.

    bool sameDog = yourDog.Equals(myDog);
    bool sameDog2 = yourDog == myDog;
    bool sameDog3 = Dog.Equals(yourDog, myDog);

By default, for a reference type, these mechanisms behave exactly the same way, implementing a reference equality check.  Under the covers, they are a bit different:

  • The Equals method is an instance method of the System.Object class.  It does some checks for null, but then uses the == operator in the default implementation.  It is a virtual method that you can override.
  • The == operator resolves to the CIL ceq instruction, which does a strict identity check.  You can override the == operator, in which case a new method named op_Equality is defined/called.

You can override Equals and the == operator independently.

#401 – Every Object Inherits an Equals Method

Every type inherits directly or indirectly from System.Object, so each has an Equals method.  This includes both value types (which inherit from System.ValueType) and reference types (which inherit from System.Object).

            public virtual bool Equals(Object object);

The Equals method returns true if the object passed in to the method is equal to the object on which the method was called.

System.Object includes a default implementation of the Equals method that :

  • For value types, returns true if the underlying values are the same (even though there are two copies of the value)
  • For reference types, returns true if the references refer to the exact same object in memory
            float f1 = 10.2f;
            float f2 = 10.2f;
            bool sameFloat = f1.Equals(f2);     // true - same value

            Dog kirby = new Dog("Kirby", 13);
            Dog kirby2 = new Dog("Kirby", 13);
            bool sameDog = kirby.Equals(kirby2);   // false - two different objects

            Dog kirby3 = kirby;
            sameDog = kirby3.Equals(kirby);    // true - two references to same object