11

IComparable only works one way

Let's say you have a Employee class. In one view, you want to show all Employees sorted by name - in another, by address. How are you going to achieve that? Not with IComparable, at least not in any idiomatic way.

IComparable has the logic in the wrong place

The interface is used by calling .Sort(). In a view showing Customer sorted by name, there is no code at all to implicate how it is going to be sorted.
On the other hand, the Customer class is assuming how it is going to be used - in this case, that it will be used in a list sorted by names.

IComparable is used implicitly

In comparison with the alternatives, it is very difficult to see where the comparing logic is being used - or if at all. Assuming your standard IDE and starting from the Customer class, I will have to

  1. Search for all references to Customer
  2. Find those references which are used in a list
  3. Check if those lists ever have .Sort() called on them

What's probably worse, if you remove an IComparable implementation that is still being used, you get no error or warning. The only thing you will get is wrong behaviour in all places that were too obscure for you to think of.

These issues combined, plus changing requirements

The very reason I came to think about this is because it went wrong for me. I have been happily using IComparable in my application for 2 years now. Now, the requirements changed and the thing needs to be sorted in 2 different ways. It have noticed that it is no fun going through the steps described in the previous section.

The question

These issues make me think of IComparable as inferior to IComparer or .OrderBy(), to the point of not seeing any valid use case that wouldn't be served better by the alternatives.
Is it always better to use IComparer or LINQ, or are there advantages/use cases I am not seeing here?

  • 2
    Your new "sort two different ways" requirement is a red herring. To solve it, all you have to do is pass a different comparator to your sort function. – Robert Harvey Mar 8 '18 at 17:32
  • @RobertHarvey Then you wouldn't be using IComparable anymore, which is reinforcing my point. – R. Schmitz Mar 8 '18 at 17:35
  • Don't forget that if you use the SortedXXX collections, they either require the stored elements to be IComparable or to have a IComparer provided. Also note, that it is trivial to reverse the natural sort order with one comparer and have it work with all IComparable objects. – Berin Loritsch Mar 8 '18 at 17:42
  • 2
    It doesn't matter that there are two different interfaces. IComparable is considered the default comparison mechanism. IComparer is used when you want to override the default comparison mechanism. – Robert Harvey Mar 8 '18 at 17:42
  • Example ReverseComparer<T>: gist.github.com/jackfarrington/078e7af7bc82482aa634 – Berin Loritsch Mar 8 '18 at 17:46
14

IComparable has the restrictions you mentioned, that is correct. It is an interface which was already available in .NET framework 1.0, where those functional alternatives and Linq were not available. So yes, one might see it as an outdated framework element which is mainly kept for backwards compatibility.

However, for lots of simple data structures, one way of sorting is probably enough or natural. For these cases, having one canonical place to implement the order relationship is still a good way to keep the code DRY, instead of always repeating the same logic in every call to OrderBy all over the place.

You have been "happily using IComparable in your application for 2 years now", as you wrote, so it appears to me it served you well for a long time. When you now have to validate, change and test all calls to Sort, it may also be a sign you were doing the same kind of sorting logic in many places, which is not the fault of IComparable. So this could be an occasion to centralize more of this logic in one place, making your code more DRY.

  • Good point about the simple data structures. However, the last paragraph doesn't make 100% sense to me. If I hadn't used IComparable, all the pre-existing sorting code would have been left untouched in their respective views, while I'd only add new sorting code for the new view. – R. Schmitz Mar 8 '18 at 17:43
  • @R.Schmitz Would the preexisting sort have worked correctly without the IComparable implementation that you wrote? – Robert Harvey Mar 8 '18 at 17:44
  • 3
    @R.Schmitz: Sure, but now you're committed to always providing a comparator (unless you use OrderBy, of course). With IComparable, you get a default implementation for free, and sometimes you don't even have to write that implementation. – Robert Harvey Mar 8 '18 at 17:48
  • 2
    @R.Schmitz: Your last comment there sums the point up nicely. I would go a little farther though. Suppose you have a numerical type like BigInteger. If it did not implement comparison operators/interfaces how would you even implement an IComparer yourself? You'd need access to the internal data structures to do it efficiently, or at all. Suppose you have a type like Customer; the public properties you wish to sort on do have comparers. For me that's the difference: implement IComparable<T> if it would be unreasonable to expect the caller to implement a comparer. – Eric Lippert Mar 9 '18 at 21:16
  • 1
    If I hadn't used IComparable, all the pre-existing sorting code would have been left untouched in their respective views, while I'd only add new sorting code for the new view. Just because IComparable was a better solution at the time, doesn't mean it is the best solution today. Your first comment here implies that "it's IComparable or nothing", which is not true, the problem could've been solved in many different ways. Applications can grow in size/scale, and things that used to look suitable may not be able to keep up with the application's increasing demands. – Flater Mar 13 '18 at 9:59
1

I agree with your sentiments about IComparable

Just look at the remarks on Array.Sort()

  • Each element of array must implement the IComparable interface to be capable of comparisons with every other element in array. (or exception is thrown)
  • If the sort is not successfully completed, the results are undefined.

We will probably never now the motivation, However! consider object.Equals() a method on every object which lets you compare objects with each other to see if they are "the same"

You have that there already, but have been tasked with adding Array.Sort() you might want to add object.Compare(object)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.