Understanding C#: Equality, IEquatable, and Equals()

By Andrew Stellman
September 29, 2010 | Comments: 4

When is one object equal to another object? It sounds like an easy question to answer when you first think about it, but it's not nearly as straightforward as you might think. But when you're working with objects, it's really useful to be able to say that object A is equal to object B. Luckily, C# and .NET give you some very handy tools to help make it easy to compare two objects and see if they're equal.

Head First C# Cover

But first, it's worth taking a minute to think about what it really means for two objects to be equal. How can you tell if object #1 is equal to object #2? Do you compare all of their properties? What about private properties or fields? Is it possible for two objects to have exactly the same state, but to not be equal? It's more complex than it seems. In this post, I'll detangle some of those ideas, and show you how to use IEquatable, the Equals() and GetHashCode() methods, and overloading the == and =! operators so that you can compare objects in your own code.

If you've got a really simple object that just represents something abstract and easy to compare—like, say, a complex number—then it's easy. If you haven't thought about complex numbers since 10th grade, they consist of two parts, a real part and an imaginary part. An example would be 7 + 3i, where 7 is the real part and 3 is the imaginary part. And you can always tell if two complex numbers are equal: just check if their real parts are equal and their imaginary pats are equal.

Here's a really simple example of some code you might write to store complex numbers as objects and compare them. It's worth your time to take a minute and actually look through it, because it's a good first step in getting a handle on what object equality actually means.

Here's the code for CompareComplexNumbers.cs.

CompareComplexNumbers.cs:

using System;

class ComplexNumber
{
     public double Real { get; set; }
     public double Imaginary { get; set; }
 
     public override string ToString()
     {
          return String.Format("{0}{1}{2}i", 
              Real, 
              Imaginary >= 0 ? "+" : "-", 
              Math.Abs(Imaginary));
      }
}

class CompareComplexNumbers
{
     public static void Main()
     {
          ComplexNumber a = new ComplexNumber() 
              { Real = 4.5D, Imaginary = 8.4D };
          ComplexNumber b = new ComplexNumber() 
              { Real = 6.3D, Imaginary = -2.3D };
          ComplexNumber c = new ComplexNumber() 
              { Real = 4.5D, Imaginary = 8.4D };
  
          Console.WriteLine("{0} equals {1}: {2}", 
              a, b, CompareTwoComplexNumbers(a, b));
          Console.WriteLine("{0} equals {1}: {2}", 
              b, c, CompareTwoComplexNumbers(b, c));
          Console.WriteLine("{0} equals {1}: {2}", 
              a, c, CompareTwoComplexNumbers(a, c));
      }
 
     static bool CompareTwoComplexNumbers(ComplexNumber A, ComplexNumber B)
     {
          // This returns true only if the real parts and the imaginary parts are equal
          return ((A.Real == B.Real) && (A.Imaginary == B.Imaginary));
      }
}

Compile and run the program from the command line—if you've only compiled C# code in Visual Studio, here's the command line to compile CompareComplexNumbers.cs into an executable:

%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\csc.exe CompareComplexNumbers.cs

NOTE: If you don't have .NET Framework 4.0 installed, replace v4.0.30319 with v3.5 in the command line to build with the .NET 3.5 compiler.

Here's what it looks like when it runs

Screenshot - CompareComplexNumbers.png

The way we figured out if two ComplexNumber objects are equal was to compare their properties: if their Real properties match and their Imaginary properties match, then they're equal. So in Main(), a and c are both 4.5 + 8.4i so they're equal, but b is 6.3 - 2.3i, so b is not equal to either a or c.

Objects that implement IEquatable know how to compare themselves

Normally, when when you want compare values in two variables, you'd use the == operator. But you already know that all things being equal, some values are more "equal" than others. The == operator works just fine for value types (like ints, doubles, DateTimes, or other structs), but when you use it on reference types you just end up comparing whether two reference variables are pointing to the same object (or if they're both null). That's fine for what it is, but it turns out that C# and .NET provide a rich set of tools for dealing with value equality in objects.

To start out, every object has a method Equals(), which by default returns true only if you pass it a reference to itself. And there's a static method, Object.ReferenceEquals(), which takes two parameters and returns true if they both point to the same object (or if they're both null). Here's an example, which you can try yourself in a console application.

Create a new Console Application project in Visual Studio. (You can also do this from the command line, but for this tutorial it's going to be a little easier from inside the IDE.)

We're going to start with my simple utility class called Guy that I use in a lot of my tutorials and posts. Add a new class to your project and call it Guy.cs. Then ahead and copy the Guy.cs code from that link and paste it into the new Guy.cs file in the IDE.

Next, add these lines to the Main() method in Program.cs:

Guy joe1 = new Guy("Joe", 37, 100);
Guy joe2 = joe1;
Console.WriteLine(Object.ReferenceEquals(joe1, joe2));  // True
Console.WriteLine(joe1.Equals(joe2));                   // True
Console.WriteLine(Object.ReferenceEquals(null, null));  // True

joe2 = new Guy("Joe", 37, 100);
Console.WriteLine(Object.ReferenceEquals(joe1, joe2));  // False
Console.WriteLine(joe1.Equals(joe2));                   // False

The comment for each line shows you what it prints to the console. Notice how the Equals() method acts just like the Object.ReferenceEquals() method—in fact, under the hood that's exactly what's being called.

But that's just the beginning. There's an interface built into .NET called IEquatable that you can use to add code to your objects so they can tell if they're equal to other objects. An object that implements IEquatable knows how to compare its value to the value of an object of type T. It has one method, Equals(), and you implement it by writing code to compare the current object's value to that of another object. There's an MSDN page that has more information about it. Here's an important excerpt:

If you implement Equals, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals method return consistent results, which the example illustrates.

MSDN, IEquatable.Equals Method

That's why you always need to override GetHashCode() any time you override Equals(). If you don't, the compiler will give you a warning.

Here's a class called EquatableGuy, which extends Guy and implements IEquatable — have a close look at all of the comments, because I explain a few things in them:

EquatableGuy.cs:

/// <summary>
/// A guy that knows how to compare itself with other guys
/// </summary>
class EquatableGuy : Guy, IEquatable<Guy>
{
 
     public EquatableGuy(string name, int age, int cash)
         : base(name, age, cash) { }
 
     /*
      * The Equals() method compares the actual values in the other Guy object's
      * fields, checking his Name, Age, and Cash to see if they're the same and only
      * returning true if they are.
      */
 
     /// <summary>
     /// Compare this object against another EquatableGuy
     /// </summary>
     /// <param name="other">The EquatableGuy object to compare with</param>
     /// <returns>True if the objects have the same values, false otherwise</returns>
     public bool Equals(Guy other)
     {
          if (ReferenceEquals(null, other)) return false;
          if (ReferenceEquals(this, other)) return true;
          return Equals(other.Name, Name) && other.Age == Age && other.Cash == Cash;
      }
 
     /// <summary>
     /// Override the Equals method and have it call Equals(Guy)
     /// </summary>
     /// <param name="obj">The object to compare to</param>
     /// <returns>True if the value of the other object is equal to this one</returns>
     public override bool Equals(object obj)
     {
          // Since our other Equals() method already compares guys, we'll just call it.
          if (!(obj is Guy)) return false;
          return Equals((Guy)obj);
      }
 
     /* 
      * We're also overriding the Equals() method that we inherited from Object, as 
      * well as GetHashCode (because of the contract mentioned in that MSDN article).
      */
 
     /// <summary>
     /// Part of the contract for overriding Equals is that you need to override
     /// GetHashCode() as well. It should compare the values and return true
     /// if the values are equal.
     /// </summary>
     /// <returns></returns>
     public override int GetHashCode()
     {
          // This is a pretty standard pattern for GetHashCode(). Note the use
          // of the bitwise XOR (^) operator, a prime number, and the conditional
          // operator (?:). I used ?: to handle nulls in GetHashCode&mdash;if the
          // property is null, it XORs the result with zero, which just means it doesn't
          // change the result at all.
          const int prime = 397;
          int result = Age;
          result = (result * prime) ^ (Name != null ? Name.GetHashCode() : 0);
          result = (result * prime) ^ Cash;
          return result;
      }
}

Once you add that class to your project, take a minute and look over exactly what's going on.

Next, add lines to the Main() method in Program.cs to use the EquatableGuy class. Here's what it looks like when you use Equals() to compare two EquatableGuy objects:

// Guy.Equals() will only return true if the actual values of the objects are the same.

joe1 = new EquatableGuy("Joe", 37, 100);
joe2 = new EquatableGuy("Joe", 37, 100);
Console.WriteLine(Object.ReferenceEquals(joe1, joe2));  // False
Console.WriteLine(joe1.Equals(joe2));                   // True

joe1.GiveCash(50);
Console.WriteLine(joe1.Equals(joe2));                   // False
joe2.GiveCash(50);
Console.WriteLine(joe1.Equals(joe2));                   // True

And now that Equals() and GetHashCode() are implemented to check the values of the fields and properties, the method List.Contains() now works. Here's a List that contains several Guy objects, including a new EquatableGuy object with the same values as the one referenced by joe1.

List<Guy> guys = new List<Guy>() { 
     new Guy("Bob", 42, 125), 
     new EquatableGuy(joe1.Name, joe1.Age, joe1.Cash), 
     new Guy("Ed", 39, 95)
};

// List.Contains() will go through its contents and call each object's Equals() method
// to compare it with the reference you pass to it.
Console.WriteLine(guys.Contains(joe1));                 // True

Now add one more thing to the Main() method. Add a line to compare joe1 and joe2, which point to instances of EquatableGuy. Even though joe1 and joe2 point to objects with the same values, == and != still compare the references, not the values themselves:

Console.WriteLine(joe1 == joe2);                        // False

Isn't there something we can do about that?

Override the == and != operators to make it easier to use your objects

If you try to compare two EquatableGuy references with the == or != operators, they'll just check if both references are pointing to the same object or if they're both null. But what if you want to make them actually compare the values of the objects? Luckily, you can overload an operator—redefining it to do something specific when it operates on references of a certain type. You can see an example of how it works in the EquatableGuyWithOverload class, which extends EquatableGuy and adds overloading of the == and =! operators. Add this class to your project:

EquatableGuyWithOverload.cs:

/// <summary>
/// A guy that knows how to compare itself with other guys
/// </summary>
class EquatableGuyWithOverload : EquatableGuy
{
     public EquatableGuyWithOverload(string name, int age, int cash)
         : base(name, age, cash) { }
 
     public static bool operator ==(EquatableGuyWithOverload left,
                                     EquatableGuyWithOverload right)
     {
          // If we used == to check for null instead of Object.ReferenceEquals(), we'd
          // get a StackOverflowException. Can you figure out why?
          if (Object.ReferenceEquals(left, null)) return false;
          else return left.Equals(right);
      }
 
     public static bool operator !=(EquatableGuyWithOverload left,
                                     EquatableGuyWithOverload right)
     {
          // Since we've already defined ==, we can just invert it for !=.
          return !(left == right);
      }
 
    /*
     * If we don't override Equals() and GetHashCode(), the IDE will give this warning:
     * 'EquatableGuyWithOverload' defines operator == or operator != but does not
     * override Object. GetHashCode().
     * 
     * Since EquatableGuyWithOverload acts just like EquatableGuy and Guy, we can 
     * just call the base methods.
     */
 
     public override bool Equals(object obj)
     {
          return base.Equals(obj);
      }
 
     public override int GetHashCode()
     {
          return base.GetHashCode();
      }
}

Now add this code to your Main() method that uses EquatableGuyWithOverload objects:

joe1 = new EquatableGuyWithOverload(joe1.Name, joe1.Age, joe1.Cash);
joe2 = new EquatableGuyWithOverload(joe1.Name, joe1.Age, joe1.Cash);
Console.WriteLine(joe1 == joe2);    // False
Console.WriteLine(joe1 != joe2);    // True

/*
 * Wait, what happened? It's calling Guy's == and =! operators. Cast to
 * EquatableGuyWithOverload to call the correct == and =!
 */


Console.WriteLine((EquatableGuyWithOverload)joe1 ==
                                        (EquatableGuyWithOverload)joe2);   // True
Console.WriteLine((EquatableGuyWithOverload)joe1 !=
                                        (EquatableGuyWithOverload)joe2);   // False
joe2.ReceiveCash(25);
Console.WriteLine((EquatableGuyWithOverload)joe1 ==
                                        (EquatableGuyWithOverload)joe2);    // False
Console.WriteLine((EquatableGuyWithOverload)joe1 !=
                                        (EquatableGuyWithOverload)joe2);    // True

You can also add Console.ReadKey() to the end to keep the command window from disappearing when you debug from inside Visual Studio. Or you can run outside the debugger (Ctrl-F5), which cases the IDE to give a "Press any key to continue. . ." prompt when your program is finished.

Now try it yourself!

One of the things that we do over and over again in Head First C# is give our readers ways to practice what they've learned. If you really want to get these concepts stuck in your head, here's an opportunity to do exactly that. See if you can start with the ComplexNumber class at the top of this post and modify it to implement IEquatable, and override != and ==. Once you've done that, you should be able to modify almost any class to support equality.

There's one more thing to think about. Up at the top of this post, I raised a few questions about figuring out if two objects are equal: Do you compare all of their properties? What about private properties or fields? Is it possible for two objects to have exactly the same state, but to not be equal?

There's no single answer to any of those questions. For some classes, you'll want to compare every field, private or otherwise. (Luckily, any object can access the private members of other instances of the same class.) Also, in some cases—like for complex numbers—you'll want to compare all of the properties. But for other cases—like the Guy class—you won't necessarily want to compare all of the properties. It depends on the application.

It's worth taking a minute to think about that last question. If you compare two guys named Joe with the same age and the same amount of money, do you want them to be equal? In your program, does that mean they're really the same guy? Is it possible for there to be two guys who both happen to be named Joe, be 37 years old, and have $100? How would you differentiate them? One way to do it is to store a unique value—like a social security number or other ID number—in a private field, and check that value in the Equals() method. If it's the same, both objects refer to the same specific guy.

Once you really start to address questions like that, it becomes clear that IEquatable, Equals(), and GetHashCode() can be very powerful tools.

Andrew Stellman is the author of Head First C# and other books from O'Reilly. You can read more from Andrew at Building Better Software.


You might also be interested in:

4 Comments

It depends on what you classes stand for. So there is no 'generic' solution to that. If there was, then System.Object would implement IEquatable, as everything inherits from object already anyway. So you could have asked the same question about Object instead of Foo.

Long story short, it depends on what you want to achieve with your class hierarchy.

Short story long, I agree it is difficult to determine what makes objects equal anyway, because even for the same object type, there might be different requirements. When you use a Cache, equality would be determined by an Entity ID of the object. When you use a Do-/Undo-List, you'd need another qualifier because the ID will probably be the same for each object in the list.

Guess I digressed a bit, but I hope I could give you some helpful points. ;-)

Definitely, those are excellent points -- 100% agreed!

A very good and informative one.

Nice ... makes sense.

News Topics

Recommended for You

Got a Question?