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 it had to look like:
- ISA Stock & Shares
- 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, but I don't like the idea of bloating my class. Besides, in a different scenario, the order may differ. I could, of course, make the Account
class implement the IComparable
interface, but again, the order may be different for each scenario, and I will have to modify the Account
class to cope with new sorting scenarios. Besides, sometimes you may not have access to modify an existing class. So a better solution would be to separate the sorting algorithm from the class you want to sort. In this way, it can vary independently (seems a favourite sentence 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. (The Sorter
class doesn't implement the IComparer
interface for reasons I will discuss later.)
How to Use CustomSort
To sort a list by a single property:
_listToBeSorted.Sort(x => x.AccountType,
"ISA Stock & Shares,ISA Cash,Wrap Cash,Personal Portfolio");
you can specify a StringComparison
to control how strings are compared when doing the sort:
_listToBeSorted.Sort(x => x.AccountEnum,
"ISA Stock & Shares,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:
var comparer = new Sorter<ClassToBeSorted, string>();
_listToBeSorted.Sort(
comparer.Add(x => x.AccountType,
"ISA Stock & Shares,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:
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:
_listToBeSorted.Sort(x => x.ClassProperty.AccountType, "wrap,Cash");
The IEnumerable.OrderBy()
method doesn't sort correctly if you want to sort by a certain element in a list. For example, if you have a class like this:
public class ListClass
{
public List<ClassToBeSorted> ClassProperty;
}
the following won't work:
_listToBeSorted.OrderBy(x => x.ClassProperty[3].AccountType);
However, you can use the custom sorter to sort by certain elements in a property of List
type:
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.:
_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
- 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. - 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. - 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
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.
The tests are written in xunit in a Behaviour Driven Development (BDD) style. When executed in ReSharper, it gives the nice documentation-like specs.
I am an independent IT contractor who is passionate about life and Microsoft technology stack. I have been programming since 2001. Besides coding, I also enjoy diving, swimming, rock climbing and travelling. I swim 2 miles a day to keep my morale and energy level consistently high.