A Typical CollectionViewSource, and the Pain of Sorting

The CollectionViewSource class is used when we want to bind to a CollectionView from XAML in addition to setting various properties such as SortDescriptions and GroupDescriptions. If your design is based on Model-View-ViewModel, you may be using it to bind your ViewModel’s observable collection to the View.

Here is an example of a CollectionViewSource, with a SortDescription defined:

    <CollectionViewSource x:Key="sourceKey"
                          Source="{Binding Path=Data}">
        <CollectionViewSource.SortDescriptions>
            <scm:SortDescription PropertyName="NameOfProp" Direction="Descending"/>
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>

The SortDescription defined above will sort the items according to the “NameOfProp” property value of each item. This is convenient, for sure, but you will most likely hit a few brick walls if you are doing anything serious. Specifically, you are going to find out that in order to see a change in sort order within the collection view, you must refresh the collection view. Not only that, but I’m betting that you will eventually come to agree with me that the sorting operation performed by these SortDescriptions is horribly slow.

On top of that, refreshing the collection view itself is horribly expensive. It’s going to block user input and result in a semi-jarring update to the view, depending on the complexity of your items. The sorting operations themselves are painful because they have to rely on the use of Reflection, which will cause a huge performance if hit if you are dealing with a large dataset of complex items.

There is a way you can overcome these limitations, however, and I will show you how within this article. This solution will boost your performance a great deal, but there’s still room for improvement (which I’ll briefly touch on later).

An Idea to Speed Sorting Up

When I was confronted with the problem of the collection view updating too slowly, I first thought about how sorting was being accomplished in the first place. First of all, the sorting behavior was being defined in the CollectionViewSource. The CollectionViewSource is a proxy to a CollectionView; it is useful in that we can switch to different views with it, etc. That aside, it struck me odd that the sorting behavior be defined at this level.

Isn’t it more appropriate to sort on the level of the CollectionView’s data source itself? Instead of relying on the slow SortDescriptions to do the sorting, we should instead remove them and sort the actual contents of the ObservableCollection.

Updates to the ObservableCollection’s items are viewable immediately within the view; it is the change in values used by a SortDescription that results in the need for a refresh. For example, if, in reponse to the values of “NameOfProp” changing, I change the actual order of the items within the collection, the results of that operation will be viewable immediately on the view. This is in contrast to simply calling Refresh() when various values of the “NameOfProp” properties have changed.

This sort of thing can be easily done using a System.Linq.Enumerable extension method like the following:

    TheCollection.OrderBy(p => p.NameOfProp);

The above will return an ordered-by-NameOfProp collection. That’s cool….how do we get this into the ObservableCollection?

The ObservableCollection maintains its items by using its Items property, which happens to be protected. So, we actually can’t use the above code to achieve sorting within our collection.

Defining a Custom ObservableCollection Type

My response to all of the above was to create a class derived from ObservableCollection: SortedObservableCollection.

This class exposes a Sort method that allows you to supply a key selector, allowing you to sort the items inside the collection. The code is more or less provided below.

    public class SortedObservableCollection<T> : ObservableCollection<T>
    {
        /// <summary>
        /// Sorts the items in the collection using the provided key selector.
    	/// </summary>
    	/// <typeparam name="TKey">Key type returned by the key selector.</typeparam>
    	/// <param name="selector">Function to retrieve the key from an item.</param>
    	public void Sort<TKey>(Func<T, TKey> selector)
    	{
    		Sort(Items.OrderBy(selector));
    	}

       /// <summary>
       /// Sorts the items in the collection using the provided key selector.
       /// </summary>
       /// <typeparam name="TKey">Key type returned by the key selector.</typeparam>
       /// <param name="selector">Function to retrieve the key from an item.</param>
       /// <param name="comparer">A <see cref="IComparer{T}"/> to compare keys.</param>
       public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer)
       {
            Sort(Items.OrderBy(selector, comparer));
       }

        /// <summary>
        /// Moves items in the inner collection to match the positions of the items provided.
        /// </summary>
        /// <param name="items">
        /// A <see cref="IEnumerable{T}"/> to provide the positions of the items.
        /// </param>
    	private void Sort(IEnumerable<T> items)
    	{
            List<T> itemsList = items.ToList();

    		foreach(T item in itemsList)
    		{
    			Move(IndexOf(item), itemsList.IndexOf(item));
    		}
    	}
    }

That’s all there is to it. Now, use this SortedObservableCollection in place of your ObservableCollections, and when you need to sort the contents, do something like:

    TheCollection.Sort(p => p.NameOfProp);

This will result in the items being sorted according to how you defined the above collection class, which in turn results in the changes being visible on the view at a speed much higher than you were seeing before when you were using just the SortDescriptions.

Thoughts

This shouldn’t be considered a “finished solution” by any means. The ObservableCollection should really be expanded on more, so that it allows multithreaded use and manipulation. The Dispatcher-thread-only nonsense of the ObservableCollection should be seen as a great limitation. Perhaps I’ll provide some of my thoughts on that some other time.

Matt Weber

I'm the founder of Bad Echo LLC, which offers consulting services to clients who need an expert in C#, WPF, Outlook, and other advanced .NET related areas. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
   
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.