IEquatable and All That: The Nine Comparisons Interfaces

In my last article I pointed out that Microsoft has given us no less than nine different general-purpose interfaces to perform the simple task of comparing objects to see if they are equal, or if one is greater than the other.

And I ended with a quick quiz. Could you identify those nine interfaces?

If you want to try that quiz, stop reading now, and check out that article, because I’m about to give the answer.

Did you just carry on reading? *Sigh*. I bet you’re the kind of person who starts watching a Pluralsight C# course, and then 5 minutes later skips to the end to see how the story finished.

Well – here’s the climax of this article (and don’t worry, noone died)… These are the nine interfaces:

IEquatable<T>
IComparable
IComparable<T>
IEqualityComparer
IEqualityComparer<T>
IComparer
IComparer<T>
IStructuralEquatable
IStructuralComparable

But listing the interfaces doesn’t help you very much – you probably want to know why there are so many of them and what they do. That’ll be easier to understand if I lay them out in a grid like this.

Equality interfaces Comparison interfaces
Natural IEquatable<T> IComparable
IComparable<T>
Plugged in IEqualityComparer
IEqualityComparer<T>
IComparer
IComparer<T>
Structural IStructuralEquatable IStructuralComparable

The interfaces on the left in this table deal with equality – or what more correctly are termed equality comparisons. In other words saying whether something equals something else. The ones on the right deal with less-then or greater than type comparisons – which confusingly, in .NET parlance tend to be referred to simply as comparisons.

I want to give you a high level overview of – broadly -what these interfaces are for, so I don’t want to get too distracted by method signatures etc., but just to give you a basic idea…

All the equality interfaces on the left of the above table expose a method called Equals() whose purpose is to identify whether two instances are equal or not. For example, this is the definition of IEquatable<T>.

public interface IEquatable<T>
{
    bool Equals(T other);
}

(The number and type of the arguments to Equals() differs between the interfaces).

On the other hand, all the comparison interfaces expose a method either called Compare() or CompareTo(), whose purpose is to determine which of two instances is the greater one. These methods all return an integer whose value tells you whether the two instances are equal, or if they aren’t which one is greater. For example, this is the definition of IComparable<T>

public interface IComparable<T>
{
    int CompareTo(T other);
}

Again, the number and types of the parameters depends on the interface.

I think it’s clear that comparisons include equality as a special case, since a side effect of doing a less than/greater than comparison is you get to find out if the two instances are equal or not. However equality and comparisons still have completely separate interfaces, and that’s probably because they tend to be used in different situations: You most commonly tend to use less-than/greater-than comparisons to sort lists of instances.

The next distinction is between what I like to call ‘natural‘ and ‘plugged in‘ equality or comparisons (That’s my terminology by the way – to my knowledge there are no corresponding ‘official’ terms).

What’s that about? Well, natural comparisons represent the way that you would normally expect comparisons to work for a particular type.

For example, if I asked you to sort this list of strings: (“Darth Vader”, “Jar Jar Binks”, “Chewbacca”) you’d probably sort them alphabetically: []. Well, maybe you are an awkward sod and you’d decide to sort them by how cool you think those StarWars characters are instead. But the computer would by default sort them alphabetically – if you doubt my word, just run this bit of code.

string[] starWarsChars = {"Darth Vader", "Jar Jar Binks", "Chewbacca"};
// this will sort the strings alphabetically
Array.Sort(starWarsChars);

That’s the natural way of sorting for strings – and internally, in this code, the Array.Sort() method will be taking advantage of the fact that string implements IComparable<string>. And, hey presto, if you look at that table I showed you earlier, you’ll see that I listed IComparable<T> as one of the interfaces to do natural comparisons.

If you want something different – say you really do want to sort those strings by how cool the characters are (yes, I know, nothing beats Jar Jar…), then you need to plug in a way of comparing strings in that way. And that means you’ll want those interfaces that deal with plugged in comparisons. Specifically, you could write a class that implements – say – IComparer<string>, and then tell the array to use that class.

Array.Sort(starWarsChars, new StarWarsCoolnessComparer());

// where elsewhere you have written
public class StarWarsCoolnessComparer : IComparer<string>
{
    // Put implementation of IComparer to compare by coolness here
}

Obviously, you have to write that comparer yourself (Seriously, why Microsoft didn’t supply a StarwarsCoolnessComparer type with the .NET framework is something that I just cannot understand. I mean we’re on .NET 4.5 for goodness sake, so there’ve been so many opportunities since .NET 1.0 to fix that omission…).

Anyway we’ve now dealt with natural and plugged in equality or comparisons. But I also listed a 3rd type of equality/comparison: Structural.

Structural equality and comparisons are a new kind of equality that was introduced with .NET 4.0 and which can be applied to collections. The idea is that a collection is structurally equal to another collection if it contains exactly the same elements in the same order. For example these two arrays are structurally equal.

string[] arr1 = {"Jar Jar Binks", "Kill! Kill!", "Aaaaargh!"};
string[] arr2 = {"Jar Jar Binks", "Kill! Kill!", "Aaaaargh!"};

If you just said arr1 == arr2, the result would be false because arrays are reference types and == does reference equality for reference types – and the two arrays are different instances – and therefore not equal according to ==.

If you want to check whether the arrays are equal in the sense of containing the same elements in the same order – Structural equality – then you can use the IStructuralEquatable interface. Like this:

bool areEqual = (arr1 as IStructuralEquatable).Equals(arr2, StringComparer.Ordinal);

Unfortunately very few collections actually support structural equality and comparisons. Further, for some reason best known to themselves, Microsoft didn’t see fit to do generic versions of the structural interfaces – which makes type safety a bit of a potential issue if you use them. All that means that that the IStructuralEquatable and IStructuralComparable interfaces are good at confusing developers who wonder what they are there for, but in my experience it’s not often that I find them particularly useful.

We’re almost done. Let’s check out our table of interfaces again – and to save you the mind-boggling effort of scrolling back up the page, I’ve reproduced it here.

Equality interfaces Comparison interfaces
Natural IEquatable<T> IComparable
IComparable<T>
Plugged in IEqualityComparer
IEqualityComparer<T>
IComparer
IComparer<T>
Structural IStructuralEquatable IStructuralComparable

Yes, this table is structurally equal to the one I showed you earlier. But it’s a different instance, so == will say they are not equal.

And more seriously, I’m hoping the purposes of the various interfaces that I have so lovingly depicted in it are now a lot clearer.

There’s just one other thing. The astute amongst you will notice that there are not one but two interfaces in many of the cells. For example to do plug-in ordering comparisons you have IComparer and IComparer<T>. That’s basically a throwback to the history of .NET, in which .NET 1.0 was released without generic support, and generics were added in .NET 2.0. Hence many interfaces have a non-generic version that was around in .NET 1.x days, and a generic version that came with .NET 2.0. IEquatable<T> is an exception because there was no .NET 1.x equivalent. In general, if you’re implementing either interface, you should implement both the non-generic and generic versions to ensure that clients that require a particular version of the interface can access the one they need.

So that’s the nine equality and comparisons interfaces.

Don’t forget if you want to know more about this topic, my C# Equality and Comparisons Pluralsight course goes into much more detail and gives many more code samples.

Advertisements

One thought on “IEquatable and All That: The Nine Comparisons Interfaces

  1. Simon,
    not only do I share your concerns on why on earth Microsoft never implemented a StarwarsCoolnessComparer but I’ve also watched 2 of your courses on Pluralsight !!! In fact I feel like stating that you take “attention to detail’ to a whole new level.
    (And no ! I haven’t watched the Collections course first!!!)
    I have most definitely cleared up a lot of “debris” on Equality and Collections in my head.

    Many many thanks for your superb guidelines and courses !

Comment on this article (your first comment on TechieSimon will be moderated)

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s