#420 – Laundry List for Implementing Value Equality and IComparable

To implement value equality (equivalence) in a reference type, you should do the following:

  • Override Object.Equals
    • In Object.Equals, call the type-specific Equals method using the as operator
  • Implement IEquatable<T> by adding a type-specific Equals method
    • Check for null using ReferenceEquals method
    • Call base class’ Equals method to compare fields that exist in the base class if it also checks for value equality
    • Check for equivalence by comparing individual fields
  • Override GetHashCode, generating a hash code based on fields used for equivalence
  • Overload == operator
    • Check for 1st parameter being null, compare to 2nd parameter
    • Call 1st parameter’s type-specific Equals method
  • Overload != operator, invoking the == operator
  • Implement IComparable<T>, adding a CompareTo method
    • Check for equivalence using Equals method, then compare individual fields
  • Overload < and > operators
    • Check for 1st parameter being null
    • Call 1st parameter’s CompareTo method
  • Overload <= and >= operators
    • Calculate a result using the <, > and == operators

#414 – Equivalence Can Be Based on a Subset of Fields

When you define value equality for a type, you typically compare all fields in the two instances to determine whether they are equivalent.  You can also use just a subset of the fields in the comparison.

Below, two Programmer instances are equivalent if their Name and Age properties match.  But their Mood properties are not used in the comparison.

    public class Programmer
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Mood { get; set; }

        // constructor omitted

        public static bool operator ==(Programmer p1, Programmer p2)
        {
            return (p1.Name == p2.Name) && (p1.Age == p2.Age);
        }

        public static bool operator !=(Programmer p1, Programmer p2)
        {
            return !(p1 == p2);
        }
    }

Test results:

            Programmer p1 = new Programmer("Sean", 47, "Elated");
            Programmer p2 = new Programmer("Sean", 47, "Surly");
            Programmer p3 = new Programmer("Bob", 47, "Surly");

            bool check = (p1 == p2);        // true
            check = (p2 == p3);             // false

#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