In the previous post we saw how a type implementor can define equality properties for its type.
All of which is good if we have control over the type which equality we want to check, but… what happens when this is not the case? It may turn out that the implementors of the type forgot to include equality strategies or that we simply want to redefine how equality is evaluated.
The designers of the .Net Framework foresaw this scenario in early in the life of the framework and provided us with IEqualityComparer. This is a very simple interface containing two methods: Compare(object, object) and GetHashCode(object).
And those who thought of the IEquatable<T>, also remembered to upgrade the IEqualityComparer to IEqualityComparer<T>, maintaining its simplicity, but with new signatures: Compare(T, T) and GetHastCode(T).
What does this mean? It means that we can implement ourselves a type that, whenever API designers estimate suitable, can change the behavior of the default equality or the one those designer had in mind.
And in our sample code?
- when the non-generic overload Equals(object, object) is used, Property4 will be evaluated
- when the generic Equals(T, T) is used, Property5 will be evaluated
- Property6 will be used to calculate the hash of the object, no matter which overload is used
As in previous part, unit tests are used as an exploratory, lie-proof tool that can be downloaded in the code repository.
But.. When? … Which?
Again, some common scenarios have been chosen, but the reader will note that there are notably fewer places when this types can be used:
- IDictionary.Add(): as before, we have tried ListDictionary as our guinea pig and it turns out that one can specify an IEqualityComparer when instantiating the class, turning into the use of the comparers Equals(object, object) instead of the default implementation of the object being added
- HashTable.Add(): again we can provide an instance to the constructor, using both Equals(object, object) and GetHashCode(object) from the IEqualityComparer
- Dictionaryt<T,K>.Add(): providing an implementation of IEqualityComparer<T> in the constructor, it allows to use the comparer method Equals(T, T) and GetHashCode(T) instead of the default ones
- Dictionary<T,K>.Contains(): providing an implementation of IEqualityComparer<T> in the constructor, it allows to use the comparer method Equals(T, T) and GetHashCode(T) instead of the default ones
- HashSet<T>.Add(): again, allows the use of an IEqualityComparer<T> for equality comparison and hash calculation
- HashSet<T>.Contains(): injected in the constructor, comparer’s methods Equals(T, T) and GetHashCode(T) will be used
We have seen that we have generic and non-generic ways to check for value equality and how .net types make use of them. As a rule of thumb, expect post-2.0 types to operate on generic overloads and fallback to the non-generic one, but a quick test before assuming anything will save you some time.
We have also seen how implementing certain interfaces we can modify how equality operates on types, using both generic and non-generic approaches.
Do not forget to have a peep at the code if you don’t believe a word of what I say (and by this time you know you shouldn’t ;-p)
Next one is type comparison. Looking forward to it, aren’t you?