Correct Way to Override Object Equality

Wednesday, 12 August 2009 04:24 AM
by Coose

Sometimes different instances of an object can be considered equal.  Take for example a Point class:

class Point

{

    public int X { get; set; }

    public int Y { get; set; }

}

Two different instances of this object that have the same X and Y values should be considered equal objects.  What happens in the following snippet?

Point first = new Point() { X = 1, Y = 2 };

Point second = new Point() { X = 1, Y = 2 };

 

Console.WriteLine("The two points {0} equal", first == second ? "ARE" : "ARE NOT");

You get “The two points ARE NOT equal”.  Additionally:

List<Point> points = new List<Point>();

points.Add(new Point() { X = 1, Y = 2 });

 

Console.WriteLine("The list {0} contain (1,2)",

    points.Contains(new Point() { X = 1, Y = 2 }) ? "DOES" : "DOES NOT");

Gives you “The list DOES NOT contain (1,2)”, even though it really does.

The framework doesn’t know that we intended the two objects to be equal if the X and Y properties are equal.  The framework will consider the objects equal if it is the same object, not if it has the same properties.  But we can surely tell the framework that these are the “same” object if their properties are the same.  It’s pretty easy, but can turn out bad if you don’t do it correctly.

The steps to follow are:

  • Override the GetHashCode method.  This method helps “find” the object based on certain properties.
  • Implement IEquitable<T> interface.  This tells the framework if the objects are actually “equal”.
  • Override the object.Equals method.  This is the non-type specific equivalent of above.
  • Create a == operator, delegates to the object.Equals method.
  • Create a != operator, the inverse of the above.

Let’s break it down:

Override GetHashCode method.

The simplest way to do that is use the exclusive or operator (^) on the GetHashCode of each “unique” property.  So, for our Point class, even if there are other properties or fields, the two that define “uniqueness” of the object are X and Y, so the GetHashCode method becomes:

public override int GetHashCode()

{

    return X.GetHashCode() ^ Y.GetHashCode();

}

Yeah…that’s it.  More unique fields/properties? Just XOR (^) the GetHashCode of them, too.

Implement IEquitable<T>.

There’s only one method: public bool Equals(T other).  This is a three step process:

  1. If the other is null, it’s not equal (obviously this is not null).
  2. If the other is this, it’s obviously equal.
  3. If all “unique” properties are equal, it’s equal.

public bool Equals(Point other)

{

    if (object.ReferenceEquals(other, null)) return false;

    if (object.ReferenceEquals(other, this)) return true;

    return this.X == other.X && this.Y == other.Y;

}

Make sure to use the object.ReferenceEquals to not get into an infinite loop/stack overflow.

Again, other “unique” fields/properties are compared here as well.

Override object.Equals.

This is the non-type specific implementation of the above step, so we compare the types, then call the overload Equals(T) above.

public override bool Equals(object obj)

{

    Point other = obj as Point;

    if (object.ReferenceEquals(other, null)) return false;

    return this.Equals(other);

}

That’s all for that.

Create a == operator.

This is a really simple step, as this operator just calls the object.Equals method.

public static bool operator == (Point x, Point y)

{

    return object.Equals(x, y);

}

Create a != operator.

An even simpler step: just return the inverse of the == operator.

public static bool operator != (Point x, Point y)

{

    return !(x == y);

}

That’s it.  Now the object is compared correctly by the framework.

So, running the original test code:

Point first = new Point() { X = 1, Y = 2 };

Point second = new Point() { X = 1, Y = 2 };

 

Console.WriteLine("The two points {0} equal", first == second ? "ARE" : "ARE NOT");

 

List<Point> points = new List<Point>();

points.Add(new Point() { X = 1, Y = 2 });

 

Console.WriteLine("The list {0} contain (1,2)",

    points.Contains(new Point() { X = 1, Y = 2 }) ? "DOES" : "DOES NOT");

 

Console.ReadLine();

Results in:

The two points ARE equal
The list DOES contain (1,2)

The full Point class is:

class Point : IEquatable<Point>

{

    public int X { get; set; }

    public int Y { get; set; }

 

    public override int GetHashCode()

    {

        return X.GetHashCode() ^ Y.GetHashCode();

    }

 

    public bool Equals(Point other)

    {

        if (object.ReferenceEquals(other, null)) return false;

        if (object.ReferenceEquals(other, this)) return true;

        return this.X == other.X && this.Y == other.Y;

    }

 

    public override bool Equals(object obj)

    {

        Point other = obj as Point;

        if (object.ReferenceEquals(other, null)) return false;

        return this.Equals(other);

    }

 

    public static bool operator == (Point x, Point y)

    {

        return object.Equals(x, y);

    }

 

    public static bool operator != (Point x, Point y)

    {

        return !(x == y);

    }

}

 

And that’s it.

Enjoy.  Or don’t.  Whatever.

Comment on this
Development
|

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading