[Download Source Project with Unit Tests]
Let's be honest - writing sorting code ranks right up there with client side script validation! - tedious, boring, horrible code that gets in the way of doing more interesting things. It is repetitive and predictable. This article will introduce a way to use one "sorter" for all your sorting requirements, amen.
Reflection is a wonderful technology which has been implemented through a powerful yet easy to use API in the Microsoft .NET Framework. The basic idea is to use code to look back at objects, methods, properties and other parts of your code. See the System.Reflection namespace documentation for more details.
The Basics of Sorting
The .NET framework already provides the general purpose QuickSort algorithm for sorting in the form of the System.Collections.ArrayList.Sort and System.Array.Sort methods which allow for sorting an ArrayList and an Array respectively. That takes care of the algorithm, but what about the individual evaluation as the elements are compared? How do those algorithm implementations do their evaluation? They use the System.IComparable interface which takes the object to be compared to and returns an integer result. The basic types all implement System.IComparable such as int, string, DateTime, etc.
How do you get these algorithms to sort your own custom objects? There are overloads on the Sort methods which take a System.Collections.IComparer. This is an interface which can be implemented to provide your own custom evaluation of two objects. Since the Microsoft Design Guidelines indicates that properties should be used to represent available information on objects then it is logical to think that your sort should be able to order your objects by their properties.
Sorting Custom Objects
The Thycotic.Collections.dll provides this exact functionality. It provides a PropertyComparer implementation of IComparer that allows you to sort your Array or ArrayList using one or more properties in ascending or descending order.
Pet pets = new Pet;
Pet pet1 = new Dog("Indiana", 3);
Pet pet2 = new Dog("Rajiv", 3);
Pet pet3 = new Dog("Chewey", 5);
Pet pet4 = new Dog("Indiana", 5);
pets = pet1;
pets = pet2;
pets = pet3;
pets = pet4;
PropertyComparer comparer = new PropertyComparer();
The simple code snippet above creates an array of Pets (specifically Dogs) and then sorts them by their Name property ascending and the Age descending. The PropertyComparer has no prior knowledge of the Pet class and does not need to since it uses Reflection to access the properties.
How to get and use the PropertyComparer
- Download Thycotic.Collections.dll from link at the top of this article
- Add a reference to the Thycotic.Collections.dll for your project
What features does Thycotic.Collections have?
- Open source (LGPL)
- Sort any objects using any properties in any order
- Sorts arrays/collections containing nulls
- Sorts by properties whose value is null
- Sorts on properties when some objects may not have the property
- Fully Test Driven code with complete unit tests
Using Reflection has associated costs. This has always been the case since late binding is not as efficient as early binding. Included in the source project is a performance unit test fixture that sorts by one and then two properties, first using the PropertyComparer and then using a single purpose comparer. The difference in performance is surprising! Due to the nature of the QuickSort algorithm the evaluations must occur many times causing the Reflection penalty to be paid over and over again. By adding some simple caching of instance property values, I was able to improve the performance of the PropertyComparer by 60-70%. The cached version was still 25-50x slower than an early bound version.
The top 3 bars are sorting on 2 properties - 1) early bound, 2) PropertyComparer with caching, 3) PropertyComparer without caching
The bottom 3 are sorting on 1 property - 1) early bound, 2) Property Comparer with caching, 3) PropertyComparer without caching
It is unlikely that performance will be an issue unless your sort is an intensively used part of your application and your application routinely experiences very heavy load - of course the decision is a trade off between convenience and performance.
I first used this concept in Java and it was one of the first things I ported over to .NET and I have used it many .NET applications since. It has also served as my example project while presenting Test Driven Development at various .NET User Groups. After much spurring from other developers (most notably Steven Smith a while back) and a deep sense of guilt, I have finally come around to sharing this time saver with others. I hope it will be as useful to you as it has been to me.
Feel free to peruse the source code and submit any bugs, enhancements or comments. Please rate this article.