Sorting an ObservableCollection<T>: ObservableSortedList<T>

How do I sort an ObservableCollection?” is a common question about WPF. The usual answer is to use a sorted view of the collection instead. Other ideas include manually sorting the observable collection using insertions and deletions, and using a simple override.

All of these approaches have some drawbacks. Using a view means that you don’t actually have a sorted collection for your code to access; ICollectionView gets you halfway there, but it’s not generic in the item type and doesn’t implement IList. Manually sorting the collection requires you to remember to do this whenever the sort order may have changed, fires off a lot of change notifications during the sort, and is inefficient as a result of using inserts/removes instead of swaps (requiring expensive shifting of trailing items) regardless of which sort algorithm one chooses. The “simple override” method isn’t too bad, but the sort order doesn’t update automatically when the items in the collection change, and thus the sort order can go stale if items change.

None of these options seemed acceptable, so I had to write my own: ObservableSortedList<T>.

Where to get it

My ObservableSortedList<T> implementation is part of the WpfCrutches project. WpfCrutches started as a collection of utilities which I was surprised to find missing from WPF. Later it picked up some things that one might not, strictly speaking, consider a “crutch”.

The latest version of the code is here: ObservableSortedList.cs. You are welcome to use just this file, without including the whole project. I don’t make any official releases of WpfCrutches, so using the whole thing will not give you any benefits in terms of stability.

Warning: this code does not currently come with tests. This code has been used by Tank Icon Maker for quite some time, so it’s somewhat tested, but I’ve never extensively tested every imaginable corner case. INotifyCollectionChanged is a pretty arcane interface (I hate it, to be honest), so exhaustive testing is certainly indicated. If you’re willing to contribute some tests, please send me a pull request on BitBucket.

License: GPLv3.

Features

ObservableSortedList<T> automatically maintains items in sorted order. Importantly, the sort order is maintained when an item already in the collection changes. That is, every time an item's properties change, the item's position in the list is updated as required to maintain the sort order.

The class implements IList<T>, and so can generally be used as a normal list. The one exception is inserting or assigning an item at a specified index. This throws a NotSupportedException to ensure you don’t accidentally rely on finding the item at that position immediately afterwards.

You can specify a custom comparer to define the sort order. If you don’t, the class uses Comparer<T>.Default. This means that if your type implements IComparable<T>, that implementation will define the sort order.

All methods which need to locate an item make use of binary search. If you want to initialize the list with a large number of items, it’s more efficient to pass them all to the constructor. Adding them one by one is slower.

You can bind to the Count property if you need to. For complex bindings to Count you could use a converter, but I personally prefer the LambdaBinding, which is also part of WpfCrutches.

Example

public void Main(string[] args)
{
    var list = new ObservableSortedList<Frob>();
    Frob x;
    list.Add(new Frob { Foo = "thingy" });
    list.Add(new Frob { Foo = "stuff" });
    list.Add(x = new Frob { Foo = "def" });
    list.Add(new Frob { Foo = "abc" });

    Console.WriteLine(string.Join(", ", list));
    // prints "abc, def, stuff, thingy"

    x.Foo = "zing";

    Console.WriteLine(string.Join(", ", list));
    // prints "abc, stuff, thingy, zing"
}

class Frob : INotifyPropertyChanged, IComparable<Frob>
{
    public string Foo
    {
        get { return _foo; }
        set { _foo = value; PropertyChanged(this, new PropertyChangedEventArgs("Foo")); }
    }
    private string _foo;

    public int CompareTo(Frob other)
    {
        if (other == null) return -1;
        return string.Compare(this.Foo, other.Foo, ignoreCase: true);
    }

    public override string ToString() { return Foo; }
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

Compared to CollectionViewSource

If you find you need to show some items in sorted order in your UI, you should seriously consider using CollectionViewSource instead. It has a rather different purpose to ObservableSortedList<T>: it is designed specifically for showing sorted collections in the UI. ObservableSortedList<T>, on the other hand, is designed to be a list.

The biggest benefit of using CollectionViewSource is that you get to change what you sort on without recreating it. This is perfect for views where the user can click a column to reverse the sort order or to sort by a different property. Additionally, this class supports filtering and grouping, while ObservableSortedList<T> supports neither.

But if you’re looking for a sorted list, and you don’t need to change the sort order or filtering, ObservableSortedList<T> might just be exactly what you’re after.

Tags: