All posts in Uncategorized

Welcome to Part 3 of our series Making a Better ObservableCollection.

If you missed Making a Better ObservableCollection Part 2 – Cross Threading you can get to it here.

We started by showing you a few neat extensions in Part 1 and then followed it up with cross-threading in Part 2. Now that we have an understanding of how to extend and offload the work to another thread, let’s cover another very important feature: Sorting.

We start by creating a custom comparer as learned here:


/// <summary>
/// Customer Sort Comparer.
/// Original Source:
/// http://blogs.msdn.com/b/jgoldb/archive/2008/08/28/improving-microsoft-datagrid-ctp-sorting-performance-part-2.aspx
/// </summary>
/// <typeparam name="T"></typeparam>
internal class CustomSortComparer<T> : ICustomSortComparer<T>
{
    #region Members

    /// <summary>
    /// A two argument delegate for comparing two objects.
    /// </summary>
    /// <param name="arg1">The arg1.</param>
    /// <param name="arg2">The arg2.</param>
    /// <returns></returns>
    protected delegate int TwoArgDelegate(T arg1, T arg2);

    /// <summary>
    /// A two argument delegate instance.
    /// </summary>
    private TwoArgDelegate _myCompare;

    #endregion

    #region Methods

    /// <summary>
    /// Sorts the specified target collection.
    /// </summary>
    /// <param name="targetCollection">The target collection.</param>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="direction">The direction.</param>
    public void Sort(ObservableCollection<T> targetCollection, string propertyName, 
        ListSortDirection direction)
    {
        // Sort comparer
        var sortComparer = new InternalSorting(propertyName, direction);

        // Sort
        var sortedCollection = targetCollection.OrderBy(x => x, sortComparer).ToList();

        // Synch
        targetCollection.SynchCollection(sortedCollection, true);
    }

    /// <summary>
    /// Performs custom sorting operation.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="direction">The direction.</param>
    internal void CustomSort(string propertyName, ListSortDirection direction)
    {
        int dir = (direction == ListSortDirection.Ascending) ? 1 : -1;

        // Set a delegate to be called by IComparer.Compare
        _myCompare = (a, b) => ReflectionCompareTo(a, b, propertyName) * dir;
    }

    /// <summary>
    /// Custom compareTo function to compare 2 objects derived using Reflection.
    /// If an aliasProperty is provided, the sort is performed on that property
    /// instead.
    /// This is ideal for columns with data types that need to be sorted by another
    /// data type.
    /// i.e. Images that need value associations, or strings with numeric entries.
    /// </summary>
    /// <param name="a">A.</param>
    /// <param name="b">The b.</param>
    /// <param name="propertyName">Name of the property.</param>
    /// <returns></returns>
    private static int ReflectionCompareTo(object a, object b, String propertyName)
    {
        // Get property value for "a"
        PropertyInfo aPropInfo = a.GetType().GetProperty(propertyName);
        var aValue = aPropInfo.GetValue(a, null);
        if (aValue == null) return 0;

        // Get property value for "b"
        PropertyInfo bPropInfo = b.GetType().GetProperty(propertyName);
        var bValue = bPropInfo.GetValue(b, null);
        if (bValue == null) return 0;

        // CompareTo method
        MethodInfo compareToMethod =
            aPropInfo.PropertyType.GetMethod("CompareTo", new[] { aPropInfo.PropertyType });
        if (compareToMethod == null) return 0;

        // Get result
        var compareResult = compareToMethod.Invoke(aValue, new[] { bValue });
        return Convert.ToInt32(compareResult);
    }

    #endregion

    #region ICompare

    /// <summary>
    /// Compares two objects and returns a value indicating whether one is less 
    /// than, equal to, or greater than the other.
    /// </summary>
    /// <param name="x">The first object to compare.</param>
    /// <param name="y">The second object to compare.</param>
    /// <returns>
    /// Value
    /// Condition
    /// Less than zero
    /// <paramref name="x" /> is less than <paramref name="y" />.
    /// Zero
    /// <paramref name="x" /> equals <paramref name="y" />.
    /// Greater than zero
    /// <paramref name="x" /> is greater than <paramref name="y" />.
    /// </returns>
    public int Compare(T x, T y)
    {
        return _myCompare(x, y);
    }

    #endregion

    #region InternalSorting

    /// <summary>
    /// Custom IComparer class to perform custom sorting.
    /// </summary>
    private class InternalSorting : CustomSortComparer<T>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="InternalSorting"/> class.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="direction">The direction.</param>
        public InternalSorting(string propertyName, ListSortDirection direction)
        {
            CustomSort(propertyName, direction);
        }
    }

    #endregion
}

Q: So, what is going on with this comparer?
A: Here is a summary:

  • Sort is called against an ObservableCollection, a property name, and a sort direction.
  • A sort comparer is derived by calling an internal class which invokes the CustomSort method.
  • The CustomSort method assesses the sort direction as an integer and then uses reflection to compare each set of values (ReflectionCompareTo).
  • A sorted collection is created against the target collection with the sort comparer applied.
  • The target collection is synched against the sorted collection.

Next, we augment our ObservableCollectionEx class:


/// <summary>
/// Extends the ObservableCollection object:
/// 1. Allows cross-thread updating to offload UI operations.
/// 2. Allows full sorting capabilities without affecting the UI thread.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableCollectionEx<T> : ObservableCollection<T>, IObservableCollectionEx<T>
{
    #region Members

    private readonly ICustomSortComparer<T> _sortComparer;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the 
    /// <see cref="ObservableCollectionEx{T}"/> class.
    /// </summary>
    public ObservableCollectionEx()
    {
        _sortComparer = new CustomSortComparer<T>();
    }

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="ObservableCollectionEx{T}" /> class.
    /// </summary>
    /// <param name="collection">The collection.</param>
    public ObservableCollectionEx(IEnumerable<T> collection) : this()
    {
        this.AddRange(collection);
    }

    #endregion

    #region Methods

    /// <summary>
    /// Sorts the observable collection by the property and sort direction.
    /// </summary>
    /// <param name="propertyName">The property within the ObservableCollectionExtender object
    /// to sort by.</param>
    /// <param name="direction">The desired sort direction.</param>
    public void Sort(string propertyName, ListSortDirection direction)
    {
        if (!this.Any()) return;
        _sortComparer.Sort(this, propertyName, direction);
    }

    /// <summary>
    /// Sorts the specified expression.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="expression">The expression.</param>
    /// <param name="direction">The direction.</param>
    public void Sort<TProperty>(Expression<Func<T, TProperty>> expression, 
    ListSortDirection direction)
    {
        if (!this.Any()) return;
        Sort(expression.GetPropertyName(), direction);
    }

    #endregion

    #region Events

    /// <summary>
    /// Source: New Things I Learned
    /// Title: Have worker thread update ObservableCollection that is bound to a ListCollectionView
    /// http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx
    /// Note: Improved for clarity and the following of proper coding standards.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)  
    {
        // Use BlockReentrancy
        using (BlockReentrancy())
        {
            var eventHandler = CollectionChanged;
            if (eventHandler == null) return;

            // Only proceed if handler exists.
            Delegate[] delegates = eventHandler.GetInvocationList();

            // Walk through invocation list.
            foreach (var @delegate in delegates)
            {
                var handler = (NotifyCollectionChangedEventHandler)@delegate;
                var currentDispatcher = handler.Target as DispatcherObject;

                // If the subscriber is a DispatcherObject and different thread.
                if ((currentDispatcher != null) &amp;&amp; (!currentDispatcher.CheckAccess()))
                {
                    // Invoke handler in the target dispatcher's thread.
                    currentDispatcher.Dispatcher.Invoke(
                        DispatcherPriority.DataBind, handler, this, e);
                }

                else
                {
                    // Execute as-is
                    handler(this, e);
                }
            }
        }
    }
}

/// <summary>
/// Overridden NotifyCollectionChangedEventHandler event.
/// </summary>
public override event NotifyCollectionChangedEventHandler CollectionChanged;

#endregion

What’s new is our 2 Sort methods. One uses a property name and the other an expression to strongly define the property. Both accomplish calling the sort comparer we just built earlier.

Q: So, why do all this? What was the point?
A: Custom sort comparers will give us the ability to increase the sorting performance of our ObservableCollections when bound to controls. It will also allow us to perform this work in an async thread instead of having to create a CollectionViewSource on the UI thread which will negatively impact the user experience during updates.

Next time, we will look at applying this to a DataGrid to greatly improve sort performance and reliability.