The Difference Between .Equals and the == Operator

I’ve just come across this, and thought it was worth a post. It’s about testing objects for equality. Try this example (the += is so that one and two don’t point to the same area of memory, two seperate strings are created):

  1. string one = "s";
  2. string two = "";
  3.  
  4. two += "s";
  5.  
  6. Console.WriteLine(one == two);
  7. Console.WriteLine(((object)one) == ((object)two));

My expected output was:

  1. true
  2. true

But the actual output was:

  1. true
  2. false

This confused me a little. I know how object instances are compared in the runtime, if the references aren’t equal then the objects aren’t equal, even if they contain the same data. This behaviour is overriden in classes such as string so that the data is compared – it doesn’t matter if both strings reference the same area of memory or not. The thing is, when you cast say, a string back to an object, and call, say GetHashCode, you get String.GetHashCode(), NOT object.GetHashCode. This can be demonstrated as follows.

We have two classes, Thing and Thing2. Thing2 inherits from Thing, both override GetHashCode, Thing returning 111, and Thing2 returning 222. We create an instance of Thing2, print its hash, then cast it to Thing, and print the hash again:

  1. class Thing
  2. {
  3.     public override int GetHashCode()
  4.     {
  5.         return 111;
  6.     }
  7. }
  8.  
  9. class Thing2 : Thing
  10. {
  11.     public override int GetHashCode()
  12.     {
  13.         return 222;
  14.     }
  15. }
  16.  
  17. class Program
  18. {
  19.     public static void Main(string[] args)
  20.     {
  21.         Thing2 t = new Thing2();
  22.         Console.WriteLine(t.GetHashCode());
  23.         Console.WriteLine(((Thing)t).GetHashCode());
  24.     }
  25. }

Even though we called GetHashCode on a Thing, we got Thing2.GetHashCode. This is in line with my expectations. So why should casting two equal strings back to objects suddenly make them unequal. I expected that we’d still be calling code in the string class. In fact, we’re not. Were using an operator to compare the two instances. Operators are static. Let’s add an operator to our Thing classes and see what happens:

  1. class Thing
  2. {
  3.     public override int GetHashCode()
  4.     {
  5.         return 111;
  6.     }
  7.  
  8.     public static bool operator ==(Thing t1, Thing t2)
  9.     {
  10.         return true;
  11.     }
  12.  
  13.     public static bool operator !=(Thing t1, Thing t2)
  14.     {
  15.         return true;
  16.     }
  17. }
  18.  
  19. class Thing2 : Thing
  20. {
  21.     public override int GetHashCode()
  22.     {
  23.         return 222;
  24.     }
  25.  
  26.     public static bool operator ==(Thing2 t1, Thing2 t2)
  27.     {
  28.         return false;
  29.     }
  30.  
  31.     public static bool operator !=(Thing2 t1, Thing2 t2)
  32.     {
  33.         return false;
  34.     }
  35. }
  36.  
  37. class Program
  38. {
  39.     public static void Main(string[] args)
  40.     {
  41.         Thing2 t1 = new Thing2();
  42.         Thing2 t2 = new Thing2();
  43.  
  44.         Console.WriteLine(t1 == t2);
  45.         Console.WriteLine(((Thing)t1) == ((Thing)t2));
  46.     }
  47. }

Above, comparing a using Thing.== always returns true, and using Thing2.== always returns false. When we try the program, we get:

  1. false
  2. true

That’s because were now calling static methods that belong to classes, not instances of classes. When comparing objects like this, the runtime selects the appropriate static operator method – in the first WriteLine it’s Thing2.==, in the second it’s Thing.==.

Going back to our string example, if we add the lines:

  1. Console.WriteLine(one.Equals(two));
  2. Console.WriteLine(((object)one).Equals(((object)two)));

to our Main method, we’ll get the following output:

  1. true
  2. false
  3. true
  4. true

Because Equals is an instance method, Thing2.Equals is called in both cases, rather than Thing2.Equals then Thing.Equals. So the pattern of calls is Thing2.==, Thing.==, Thing2.Equals, Thing2.Equals.

The key to this is understanding the difference between static and instance methods. Static methods aren’t normally called through instances. In the case of operators, the runtime identifies which class’s operator to use. Just one to watch out for.

Share and Enjoy:
  • Print
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • email
  • LinkedIn
  • Technorati

Leave a Reply

Your email address will not be published. Required fields are marked *