Overriding Equals in C# (Part 3)

This post is part three in a series:

(View the completed example)

Overriding Equality Operators

Phew, we made it: we have now successfully implemented Equals and GetHashCode.

Let’s wrap things up by overriding the == operator.

Although Equals now compares objects using value equality, the == operator still compares objects with reference equality, leading to the following inconsistent behavior:

PhoneNumber numberX = new PhoneNumber { AreaCode = "123", Exchange = "456", SubscriberNumber = "7890" };
PhoneNumber numberY = new PhoneNumber { AreaCode = "123", Exchange = "456", SubscriberNumber = "7890" };

numberX.Equals(numberY); // TRUE
numberX == numberY; // FALSE

We can fix this by overriding the == operator:

public static bool operator ==(PhoneNumber numberA, PhoneNumber numberB)
{
    // Implementation
}

When we implement this, we need to be very careful not to use == as we could accidentally end up calling our override method again, resulting in an endless loop. Alright, let’s do this:

public static bool operator ==(PhoneNumber numberA, PhoneNumber numberB)
{
    // Check if either of the numbers are null
    if(Object.ReferenceEquals(null, numberA) || Object.ReferenceEquals(null, numberB))
    {
        return false;
    }

    // Check if the numbers are the same number
    if(Object.ReferenceEquals(numberA, numberB))
    {
        return true;
    }

    return numberA.Equals(numberB);
}

Simplifying this further:

public static bool operator ==(PhoneNumber numberA, PhoneNumber numberB)
{
    return !Object.ReferenceEquals(null, numberA)
        && !Object.ReferenceEquals(null, numberB)
        && (Object.ReferenceEquals(numberA, numberB) || numberA.Equals(numberB));
}

As Nathan Jackson points out (thanks Nathan!) in the comments below, this implementation leads to a situation where, if both instances are null, they will not be considered equal:

PhoneNumber phoneNumberA = null;
PhoneNumber phoneNumberB = null;

phoneNumberA == phoneNumberB; // FALSE, even though they are both the same value (null)

We can address this by simplifying our implementation even further:

public static bool operator ==(PhoneNumber numberA, PhoneNumber numberB)
{
    return (Object.ReferenceEquals(numberA, numberB) || numberA.Equals(numberB));
}

However, as John points out (thanks John!) in the comments below, this implementation leads to a subtle bug when we try to call Equals on numberA:

PhoneNumber phoneNumberA = null;
PhoneNumber phoneNumberB = new PhoneNumber();

// Throws a null reference exception
if (phoneNumberA == phoneNumberB)
{
}

We can fix this by adjusting our solution slightly (thanks John!):

public static bool operator ==(PhoneNumber numberA, PhoneNumber numberB)
{
    if (Object.ReferenceEquals(numberA, numberB))
    {
        return true;
    }

    // Ensure that "numberA" isn't null
    if(Object.ReferenceEquals(null, numberA))
    {
        return false;
    }

    return (numberA.Equals(numberB));
}

Before we can finish this up, we also need to implement the opposite not equals operator (!=). As long as we’re careful not to use != in our implementation, we can simply point back to our implementation of the == operator:

public static bool operator !=(PhoneNumber numberA, PhoneNumber numberB)
{
    return !(numberA == numberB);
}