Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Custom Sort a List of Objects by Any Order

4.93/5 (16 votes)
14 Mar 2012CPOL4 min read 80.9K   92  
An IList Extension Method that sorts the list in any order or in a combination of orders.

Introduction

Recently, I encountered a problem at my work - a list of account objects needed to appear in a specific order in a report and the order had to look like:

  • ISA Stock 
  • ISA Cash 
  • Wrap Cash 
  • Personal Portfolio 

The order is by the Account object's AccountType property. Apparently, I cannot use the IEnumerable.OrderBy() method as the order required is not alphabetical. I could add an extra property - Order - to the Account object so that ISA Stock accounts will have Order = 1; ISA Cash accounts have Order = 2, etc, then sort the accounts by Order property. But I don't like the idea of bloating my class just for sorting. Besides, in a different scenario, the order may differ.

Another possible solution is to make the Account class implement the IComparable interface, but again, the order may be different for different scenarios, and implementing IComparable can only deal with one specific order. Besides, sometimes you may not have access to modify the class you want to sort. So a better solution would be to separate the sorting algorithm from the objects you want to sort. In this way, they both can vary independently - a cliche in the GoF Design Pattern book :)  

So I ended up developing an IList Extension Method and a Sorter class which mimics the syntax of IEnumerable.OrderBy() and lets you custom sort (rather than alphabetical) a list of objects by its string or enum properties. (Contrary to common belief, my Sorter class doesn't implement the IComparer interface for reasons I will discuss later.) 

Download the Source Code  

Thanks to Paw Jershauge for pointing out the source code download location:

http://www.codeproject.com/KB/106570/Collections/CustomSort.zip   

How to Use CustomSort 

To sort a list by a single property: 

C#
_listToBeSorted.Sort(x => x.AccountType, "ISA Stock,ISA Cash,Wrap Cash,Personal Portfolio");

you can specify a StringComparison to control how strings are compared when doing the sort:

C#
_listToBeSorted.Sort(x => x.AccountType, "ISA Stock,ISA Cash,Wrap Cash,Personal Portfolio", 
                                          StringComparison.InvariantCultureIgnoreCase);

If you want to sort by more than one property, you have to use the Sorter class. I employed the Fluent Interface technique to make the code more readable:

C#
var sorter = new Sorter<ClassToBeSorted, string>(); 

_listToBeSorted.Sort(
    sorter.Add(x => x.AccountType, "ISA Stock,ISA Cash,Wrap Cash,Personal Portfolio") 
          .Add(x => x.AccountName, "Peter,Adam", StringComparison.InvariantCultureIgnoreCase)
          .Add(x => x.AccountLocation, "London,Edinburgh") 
); 

If your object contains other objects as properties, for example:

C#
public class CompositeClassToBeSorted { 
   public ClassToBeSorted ClassProperty; 
   public string ClassName; 
}

you can drill down as deep as you want to use a certain property for sorting:

C#
_listToBeSorted.Sort(x => x.ClassProperty.AccountType, "Wrap,Cash");

The IEnumerable.OrderBy() method have the same capability but it doesn't sort correctly if the to-be-sorted object property is a collection of other objects and you want to sort by a certain element in the collection. For example, if you have a class like this:

C#
public class ListClass
{
    public List<ClassToBeSorted> ClassProperty;
}

the following won't work:

C#
_listToBeSorted.OrderBy(x => x.ClassProperty[3].AccountType);

However, you can use my custom sorter to sort by certain elements in a property of List type:

C#
classList.Sort(x => x.ClassProperty[0].AccountType, "Wrap,Cash");

The Sorting Behaviour, Implementation, and Performance 

If your whole list contains A, B, C, D, E, F, and you only specify partial order, i.e.:

C#
_listToBeSorted.OrderBy(x => x.AccountType,"B,E"); 

then B, E will come on top of the list, and the rest of the elements will remain where they are.

Initially, I made CustomSort to implement the IComparer interface and use the IList.Sort(IComparer) method to sort the list. It works like a charm if the custom sort order contains every element in the list. But when we only specify a partial order, the remaining elements don't stay where they are, but kind of randomised (or maybe, there is a pattern that I failed to see). So I ended up sorting the list myself.

The performance could be better. I tested it with a list of 1000 objects. If the sort is only by one property, sorting completes in a split second. But if you sort by two or three properties, the sort will take a few seconds. I have included a performance test in the attached source file. You can see for yourself whether the performance is satisfactory for you.

Possible Improvements

  1. At the moment, the custom sort only works for the generic IList. It is easy enough to do the same for IEnumerable. It is just that I found IList to be the most common collection, and you can convert IEnumerable to List easily by calling the ToList() method.
  2. The custom sort can sort by a property which is a List, classList.Sort(x => x.ClassProperty[0].AccountType, "Wrap,Cash"), but it doesn't do so for Dictionary type properties. Again, this can be easily achieved.
  3. Performance can be improved, but I haven't been bothered to try out various sorting algorithms. I just did enough to satisfy the requirement of my own work situation.

About the Source Code and Unit Tests 

The source code is in .NET 3.5. Some of the .NET 4.0 features, like optional parameters, dynamics, etc., will make the code more concise and maybe more efficient, but for better backwards compatibility, I stuck to .NET 3.5 for now. The project file should open and convert fine in VS2010 (I tested and it works). I will aim to provide a copy of the source code in .Net 4.0 soon.  

The tests are written in xunit in a Behaviour Driven Development (BDD) style. When executed in ReSharper, it gives the nice documentation-like specs. To run the tests, your reSharper needs to have the xunit test runner plugin installed.  

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)