2

Ok, I have a class within which I want to over-ride the equality operator, so I have the following code:

/// <summary>
/// Over-ride of the equality operator.
/// </summary>
/// <param name="credential1">The left hand object to test for equality.</param>
/// <param name="credential2">The right hand object to test for equality.</param>
/// <returns>True if the objects are equal.</returns>
public static bool operator ==(KnownServerCredential credential1, KnownServerCredential credential2)
{
    // ok check if either is null
    bool cell1Null = Equals(null, credential1);
    bool cell2Null = Equals(null, credential2);

    // if both are, then it's true
    if (cell1Null && cell2Null)
    {
        return true;
    }

    // if only one is, then how can they be the same?
    if (cell1Null || cell2Null)
    {
        return false;
    }

    // neither are - so we can now go to full on equality
    return credential1.IsEqualTo(credential2);
}

This works fine, and I'm happy with it. However, static analysis tools (both Resharper and the VS2010 Code Analysis) swear blind that the last line could throw a null reference exception, because of the way I am checking for null in the top two lines. If I change the top two lines from Equals(null, credentialX) to credentialX == null then the static analysis tools are happy, but it creates a stack overflow exception, because I am then recursively calling the equality over-ride. I can have the best of both worlds by using (object)credentialX == null, but that doesn't seem like the cleanest way of doing it.

So the simple question is, am I missing something, or is cast-and-compare the best way to achieve what I'm looking for?

Matt Whitfield
  • 6,436
  • 3
  • 29
  • 44
  • 1
    Object.ReferenceEquals might work ? – Kek Jul 19 '12 at 11:26
  • please do not forget about `.GetHashCode()`! –  Jul 19 '12 at 11:29
  • @AndreasNiedermair he hasn't overridden `Equals`, so it would be *incorrect* to override `GetHashCode` – Marc Gravell Jul 19 '12 at 11:36
  • @MarcGravell If the OP doesn't override `.Equals` - yes ... but I assumed that (I thought of an isolated example here ...) - thanks ... unfortunately 6mins, so I cannot add this as a clarification, but I think your comment should suffice! –  Jul 19 '12 at 11:37
  • Sorry, I didn't want to include the whole class as a distraction, I have over-ridden both, and I do understand the implications there, I just wanted to know about the best style in this particular small instance :) – Matt Whitfield Jul 19 '12 at 11:43

2 Answers2

3

There are two ways of checking whether a pair of objects are equal: reference equality and structural/value equality. Reference equality is the default for all reference types (classes), and structural equality is the default for all value types (but the default implementation is not optimal). Use the following guide to implement structural equality for both reference types and value types.

Equality

The equality check should follow these rules:

  • An object is equal to itself (Identity)
  • Comparing x to y returns the same truth as comparing y to x. (Symmetry)
  • If x is equal to y and y is equal to z, then x must be equal to z. (Transitivity)
  • An object is never equal to null.
  • null is equal to null.
  • No exceptions should be thrown.

Let your class or struct implement the IEquatable<T> interface for custom equality checking, then implement the IEquatable<T>.Equals(T) and Object.Equals() methods.

Equality for reference types (classes)

For reference types, implement the IEquatable<T>.Equals(T) method like this:

public bool Equals(MyType other)
{
    if (Object.ReferenceEquals(other, null) ||      // When 'other' is null
        other.GetType() != this.GetType())          // or of a different type
        return false;                               // they are not equal.
    return this.field1 == other.field1
        && this.field2 == other.field2;
}

Then override Object.Equals() like this:

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

Equality for value types (structs)

Since value types cannot be null, implement the IEquatable<T>.Equals(T) method like this:

public bool Equals(MyType other)
{
    return this.field == other.field
        && this.field2 == other.field2;
}

Then override Object.Equals() like this:

public override bool Equals(object obj)
{
    if (!(obj is MyType))
        return false;
    return Equals((MyType)obj);
}

Equality operators

For both reference and value types, you may want to override the default equality and inequality operators. Based on this post by Jon Skeet the equality and inequality operators can be implemented like this:

public static bool operator ==(MyType left, MyType right)
{
    return Object.Equals(left, right);
}

public static bool operator !=(MyType left, MyType right)
{
    return !(left == right);
}

Note that when left and/or right is null, Object.Equals(object, object) does not call the Object.Equals(object) override (and therefore not the IEquatable<T>.Equals(T) method).

Hash code

Sometimes the hash code of an object is important, for example when the object might be put in a dictionary or hash table. In general, when you override the Equals() method, override the GetHashCode() method. The hash code should follow these rules:

  • The hash code should never change, not even after modifying some fields in the object.
  • The hash code must be equal, for objects that are considered to be equal.
  • The hash code may be anything (including equal) for objects that are considered to be unequal.
  • The hash codes should be randomly distributed.
  • The hash code function must never throw an exception and must always return.
  • The hash code should be calculated very fast.

So to implement Object.GetHashCode() for a class or struct that uses structural equality, choose some fields from your object that are immutable and mark them readonly. Use only those fields to calculate the hash code. Override the Object.GetHashCode() method and implement it like this:

public override int GetHashCode()
{
    unchecked
    {
        int hash = 17;
        // Don't forget to check for null values.
        hash = hash * 29 + field1.GetHashCode();
        hash = hash * 29 + field2.GetHashCode();
        // ...
        return hash;
    }
}

Or, if you have only one immutable field, you may consider just using:

public override int GetHashCode()
{
    // Don't forget to check for null values.
    return field1.GetHashCode();
}

If you have no immutable fields, return a constant hash code. For example, the hash code of the type itself.

public override int GetHashCode()
{
    return GetType().GetHashCode();
}

Structural equality for collections

Collections should not be compared using the default Equals() method. Instead, the default equality for collections should be reference equality. To also implement structural equality, implement the IStructuralEquatable interface. For example:

bool IStructuralEquatable.Equals(object obj, IEqualityComparer comparer)
{
    var other = obj as MyType;
    if (other == null)
        return false;
    return ((IStructuralEquatable)this.innerArray)
        .Equals(other.innerArray, comparer);
}

int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
{
    return ((IStructuralEquatable)this.innerArray).GetHashCode(comparer);
}
Community
  • 1
  • 1
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • please do not forget about `.GetHashCode()`! –  Jul 19 '12 at 11:29
  • I was just adding that as you commented :). – Daniel A.A. Pelsmaeker Jul 19 '12 at 11:31
  • 1
    `` another thing: your notation is incorrect ... "this is the implementation of `IEquatable.Equals`" should state "this is the implementation of `IEquatable.Equals(KnownServerCredential other)`" (because otherwise you would really mean `IEquatable.Equals` from the base-interface), and it's not `Object.Equals()` (because this would be the static one) - it's just `.Equals()`, and so on ...`` –  Jul 19 '12 at 11:33
  • 1
    Instead of using `if (other == null)` you should use `Object.ReferenceEquals` directly. The end result is nearly similar, but using the operator== inside this block of code is a recipe for disaster. – xanatos Jul 19 '12 at 11:36
  • This is nice - satisfies the static analysis and makes my code read much more pleasantly. Accepted. Thanks. – Matt Whitfield Jul 19 '12 at 11:57
2

Normally when I have to check for equality to null, I use:

if (object.ReferenceEquals(myref, null))
{

it has the advantage that it bypasses overloaded Equals and operator==.

xanatos
  • 109,618
  • 12
  • 197
  • 280