For years, I thought I had the one, true answer to Equals() from seeing something in some MSDN article a long, long time ago – like 2002 or 2003. Or maybe it was on Brad’s or Krystof’s blog and freaked out because I wasn’t doing it. Whatever the case, I’d make sure to point out the “proper” way to do Equals() to my colleagues. And I always made sure that I’d do it the same way for all my types that needed Equals overridden. Then I decided to measure it.
So, what did I think the best way to do Equals was? Consider this type:
class MyClass { public int NumberValue; public string StringValue; }
If I were to write Equals the way I used to, I would write it the following way:
public override bool Equals(object obj) { if (obj == null || obj.GetType() != GetType()) return false; if (ReferenceEquals(obj, this)) return true; MyClass other = (MyClass) obj; return other.NumberValue == this.NumberValue && other.StringValue == this.StringValue; }
Note that the above implementation suffices the conditions for a robust Equals. The important part of Equals() is that it covers all the following cases:
- it returns false if obj is null;
- it returns false if obj is not the same type as this;
- it returns true if the references are the same;
- it doesn’t throw Exceptions; and
- it doesn’t allocate memory.
The actual evaluation of equality is per class and changes for every class. In the example above, the equality evaluation is the return statement comparing the string and the int of both MyClass instances. The above list of conditions is boilerplate and should be met for every Equals method you write.
So what’s the problem? My Equals method does everything in that list just fine. Right?
Two of the conditions are trivial to meet: the check for null and check for reference equality. The hard one to meet, perhaps because are there so many ways of doing it, is checking for the right type. In my method above, I check the type by comparing GetType() of both obj and this. If they aren’t equal, I return false. That turns out to be 5 times slower than the other two ways of doing it: the is and as operator.
The .NET Design Guidelines recommend you use the as operator to check the type rather than the is operator because it does the type check and assignment all at once. So let’s re-write the Equals method to use the as operator:
public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; MyClass other = obj as MyClass; if (other != null) return other.NumberValue == this.NumberValue && other.StringValue == this.StringValue; return false; }
This method meets all the conditions of a good Equals, but has the advantage of being pretty fast, faster than the first way I did it anyway. Since the gurus in Redmond recommend the as operator, you’d think that it’s the fastest: wrong! Check it:
public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; if (obj is MyClass) { MyClass other = (MyClass) obj; return other.NumberValue == this.NumberValue && other.StringValue == this.StringValue; } return false; }
Equals with the is operator and then casting is actually the fastest of them all (by about 10% when compared to the as operator). All three methods meet the conditions of a good Equals method, but the least intuitive one – to me at least – has the advantage of being the fastest. And it’s cheap speed, too: you get it just by implementing Equals the same way everytime for every type. You generally want Equals to be pretty fast because it will show up a lot in loops and operations on collections.
My point? Always measure – don’t assume you’re doing things right. It’s good to go back and think about the fundamentals once in a while.