Yep, it’s true: I’m still alive! After a long pause on blogging (due to a future project which I’ll talk about in a future post), I’m back…And with a very interesting topic. Comparison between objects is something which developers tend to do a lot. By default, all objects inherit the Object’s Equals virtual method, which returns true if both objects refer to the exactly the same object. According to the excellent CLR via C#, here’s how that code might look like (I say might look because equality is implemented as an extern method):
if (this == obj) return true;
In other words, you’re running an identity check. This approach might not be enough for you. In fact, many people say that the base Equals method implementation should look like this:
//if obj is null, then return false
if (obj == null) return false;
//check for types
if (this.GetType() != obj.GetType()) return false;
//check for field values
There’s a lot going on here. We start by checking against null (obviously, if obj is null, we can simply return false). We then proceed and compare the object’s types. If they don’t match, then false it is. Finally,we need to check the fields values of both objects. Since Object doesn’t have any fields,then we can return true. Now, there are two conclusions you can take from the previous snippet:
- the first, is that you shouldn’t really use Equals to perform identity checks because Equals is virtual and that means that a derived class might change the way that method works. If you want to perform an identity check, then you should use the static ReferenceEquals method (defined by the Object type).
- the second is that having a poor Equal’s implementation means that the rules for overriding it are not as simple as they should be. So, if a type overrides the Equals method, it should only call the base class’ method if the base class isn’t Object.
To make things more complex, we should also take the ValueType class into consideration. Interestingly, it overrides the Equals method and uses a similar algorithm to the last one we showed. Since it has to ensure the equality checks for its fields, it needs to resort to reflection. In practice, this means that you should provide your own implementation of the Equals methods when you create new value types classes.
When you’re creating new type, you should always ensure that it follows four rules:
- Equals must be reflexive: x.Equals(x) must always return true.
- Equals must be symmetric: x.Equals(y) should return the same result as y.Equals(x).
- Equals must be transitive: x.Equals(y) == true && y.Equals(z) == true =>x.Equals(z) == true.
- Equals must be consistent: calling the method several times with the same values should return the same results.
Now, these are the basic guarantees you need to give. There are a couple of extras things you can do too. For instance, you could make your type implement the IEquatable<T> interface to perform equals comparisons in a type safe manner. You *should* also overload the == and != operators. Their internal implementations should always reference the Equals override you’ve written.
Finally, if your object will be used in comparison operations, then you should go ahead and implement the IComparable<T> interface and overload the <, <=, > and >= operators (once again, the operators’ implementation should reuse the IComparable<T>’s CompareTo method). Hashcodes are also influenced by custom Equals method implementation, but we’ll live that discussion for a future post. Stay tuned for more.