UPDATE from 2019: This post was originally on Coverity’s “Ask The Bug Guys” developer blog but that was taken down; an archived version is here. The formatting is still a bit messed up; I’ll fix it later.
Today’s episode of Ask The Bug Guys features a C# question from reader Jan:
Hi Bug Guys! We recently had a bug in our code comparing
shorts, where calling
Equals()produced different results from using the
==operator. Also, the behaviour of
Equals()was not symmetric. Here’s a code snippet that reproduces the behaviour we observed:
We were quite surprised when we found this. What explains this difference? Is it better to use
Equals()when comparing integer types?
Hi Jan! Thanks for the great question.
C# was designed to be a “pit of success” language: that is, a language where the obvious technique and the correct technique are the same. And for the most part, that’s true. Unfortunately, equality is one of the areas where there are significant pits of failure, and you’ve fallen right into one of them.
I’m going to add some additional cases to your program to illustrate a number of different equalities.
What the heck? As it turns out, we have many different kinds of equality demonstrated here.
In scenarios one and two we must first determine what the
== operator means. C# defines over a dozen different built-in
There is no
int == short or
short == int operators, so the unique best match on the list of built-in operators must be determined. It turns out that the best match is
int == int. So the
short is converted to
int and then the two values are compared as numbers. They are therefore equal.
In scenario three we must first solve an overload resolution problem to determine what
Equals means. The receiver is of type
int and it has three methods named
The first one we can eliminate because there are not enough arguments. Of the other two, the unique best method is the one that takes an
int; it is always better to convert the
short argument to
object. Therefore we call
Equals(int), which then compares the two integers again using value equality, so this is true.
In scenario four we again must determine what
Equals means. The receiver is of type
short which again has three methods named
Overload resolution eliminates the first because there are too few arguments and eliminates the third because there is no implicit conversion from
short. That leaves
short.Equals(object), which has the moral equivalent of this implementation:
That is, for this method to return true the argument passed in must be a boxed
short, and when unboxed it must be equal to the receiver. Since the argument is a boxed
int, this returns false. There is no special gear in this implementation that says “well, what if I were to convert myself to the type of the argument and then compare?”
In scenarios five, six and seven operator overload resolution chooses the
object == objectform, which is equivalent to a call to
Object.ReferenceEquals. Clearly the two references are equal in case five and unequal in cases six and seven. Whether the values of the objects when interpreted as numbers are equal does not come into play at all; only reference equality is relevant.
In scenarios eight and nine operator overload resolution chooses the static method
Object.Equals, which you can think of as being implemented like this:
In scenario eight we have two references that are unequal and not null; therefore we call
int.Equals(object), which as you would expect from our previous discussion of
short.Equals(object) is implemented as the moral equivalent of:
Since the argument is of type
int it is unboxed and compared by value. In scenario nine the argument is a boxed
short and so the type check fails and this is false.
Summing up: I’ve shown nine different ways that two things can be compared for equality; despite the fact that clearly in every case we have the number one on both sides of the equality, equality is true in only half the cases. If you think this is crazy and confusing, you’re right! Equality is tricky in C#.
I’ve been looking a lot at confusing cases for equality over my last year at Coverity; two of our checkers are specifically designed to find situations where you used the wrong kind of equality. But this article is long enough already and I’ve answered your question (I hope!), so I’ll discuss the specific cases that our checkers look for in another posting.