It was pushed on me this week to modify a windows forms application I have been working on so that a DataGridView control was sortable. Since i’m a web guy, this turned out to be quite a challenge. There didn’t seem to be an immediately clear way to go through with this, short of dumping my data set (a generic List) into a dataset.

I proceeded to attempt to make collection object to implement BindingList, but a few stabs at that only demonstrated that I was going to spend far too much time making a one-time-use solution.

After consulting the Google, I came across a generic implementation of BindingList that uses more Linq then I understand to enable controls to automatically sort the dataset with no code-behind needed. Best of all, for performance, it will cache the generated code in a dictionary so that it doesn’t have to execute redundant reflection code (which can be costly, for sure).

I ended up modifying the class a small amount to remove the direction toggle logic in the ApplySortCore method which would sort a column in the opposite direction as requested under some circumstances, apply some better code commenting, and to apply strong types to some variables (as opposed to declaring them with ‘var’).

And now ..

  1 namespace ExampleIncorporated.BigProduct
  2 {
  3     using System;
  4     using System.Collections.Generic;
  5     using System.ComponentModel;
  6     using System.Linq;
  7     using System.Linq.Expressions;
  8     using System.Reflection;
  9  
 10     /// <summary>
 11     /// Provides a generic method of storing objects in a bindable and sortable collection.
 12     /// </summary>
 13     /// <typeparam name="T">The type of object stored in the collection.</typeparam>
 14     public class SortableBindingList<T> : BindingList<T>
 15     {
 16         // reference to the list provided at the time of instantiation
 17         List<T> originalList;
 18         ListSortDirection sortDirection;
 19         PropertyDescriptor sortProperty;
 20  
 21         /// <summary>
 22         /// Used to store a local cache of generated methods used to sort the collection.
 23         /// </summary>
 24         /// <remarks>
 25         /// This in place so that the method used to sort a property will not need to be generated multiple times.
 26         /// </remarks>
 27         Action<SortableBindingList<T>, List<T>>
 28                        populateBaseList = (a, b) => a.ResetItems(b);
 29  
 30         // a cache of functions that perform the sorting
 31         // for a given type, property, and sort direction
 32         static Dictionary<string, Func<List<T>, IEnumerable<T>>>
 33            cachedOrderByExpressions = new Dictionary<string, Func<List<T>,
 34                                                      IEnumerable<T>>>();
 35  
 36         /// <summary>
 37         /// Initializes a new instance of the <see cref="SortableBindingList&lt;T&gt;"/> class.
 38         /// </summary>
 39         public SortableBindingList()
 40         {
 41             originalList = new List<T>();
 42         }
 43  
 44         /// <summary>
 45         /// Initializes a new instance of the <see cref="SortableBindingList&lt;T&gt;"/> class.
 46         /// </summary>
 47         /// <param name="enumerable">The enumerable.</param>
 48         public SortableBindingList(IEnumerable<T> enumerable)
 49         {
 50             originalList = enumerable.ToList();
 51             populateBaseList(this, originalList);
 52         }
 53  
 54         /// <summary>
 55         /// Initializes a new instance of the <see cref="SortableBindingList&lt;T&gt;"/> class.
 56         /// </summary>
 57         /// <param name="list">The list.</param>
 58         public SortableBindingList(List<T> list)
 59         {
 60             originalList = list;
 61             populateBaseList(this, originalList);
 62         }
 63  
 64         /// <summary>
 65         /// Gets a value indicating whether the list is sorted.
 66         /// </summary>
 67         /// <value></value>
 68         /// <returns>true if the list is sorted; otherwise, false. The default is false.</returns>
 69         protected override bool IsSortedCore
 70         {
 71             get
 72             {
 73                 return sortProperty != null;
 74             }
 75         }
 76  
 77         /// <summary>
 78         /// Sorts the items if overridden in a derived class; otherwise, throws a <see cref="T:System.NotSupportedException"/>.
 79         /// </summary>
 80         /// <param name="prop">A <see cref="T:System.ComponentModel.PropertyDescriptor"/> that specifies the property to sort on.</param>
 81         /// <param name="direction">One of the <see cref="T:System.ComponentModel.ListSortDirection"/>  values.</param>
 82         /// <exception cref="T:System.NotSupportedException">Method is not overridden in a derived class. </exception>
 83         protected override void ApplySortCore(PropertyDescriptor prop,
 84                                 ListSortDirection direction)
 85         {
 86             // Update our local variables with the values given to us by the sorting control
 87             sortDirection = direction;
 88             sortProperty = prop;
 89  
 90             // Get the name of our method based on the sort direction
 91             string orderByMethodName = sortDirection ==
 92                 ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
 93  
 94             // Get the key used to cache the method based on the type GUID and method name
 95             string cacheKey = string.Concat(
 96                 typeof(T).GUID,
 97                 prop.Name,
 98                 orderByMethodName);
 99  
100             // If the method is not in our cache, generate the sort method
101             if (!cachedOrderByExpressions.ContainsKey(cacheKey))
102                 CreateOrderByMethod(prop, orderByMethodName, cacheKey);
103  
104             // Execute the sort method on the base list
105             ResetItems(cachedOrderByExpressions[cacheKey](originalList).ToList());
106  
107             // Re-bind the collection
108             ResetBindings();
109         }
110  
111  
112         /// <summary>
113         /// Creates the order by method.
114         /// </summary>
115         /// <param name="prop">The property to sort by.</param>
116         /// <param name="orderByMethodName">Name of the order by method.</param>
117         /// <param name="cacheKey">The cache key.</param>
118         private void CreateOrderByMethod(PropertyDescriptor prop,
119                      string orderByMethodName, string cacheKey)
120         {
121  
122             ParameterExpression sourceParameter = Expression.Parameter(typeof(List<T>), "source");
123             ParameterExpression lambdaParameter = Expression.Parameter(typeof(T), "lambdaParameter");
124  
125             // Get the type of the sort property
126             PropertyInfo accesedMember = typeof(T).GetProperty(prop.Name);
127  
128             // Create an expression that selects the value of our sort property
129             LambdaExpression propertySelectorLambda =
130                 Expression.Lambda(Expression.MakeMemberAccess(lambdaParameter,
131                                   accesedMember), lambdaParameter);
132  
133             // Create our method
134             MethodInfo orderByMethod = typeof(Enumerable).GetMethods()
135                                           .Where(a => a.Name == orderByMethodName &&
136                                                        a.GetParameters().Length == 2)
137                                           .Single()
138                                           .MakeGenericMethod(typeof(T), prop.PropertyType);
139  
140             // Create the linq order-by method
141             var orderByExpression = Expression.Lambda<Func<List<T>, IEnumerable<T>>>(
142                                         Expression.Call(orderByMethod,
143                                                 new Expression[] { sourceParameter,
144                                                                propertySelectorLambda }),
145                                                 sourceParameter);
146  
147             // Add the compiled method to the cache
148             cachedOrderByExpressions.Add(cacheKey, orderByExpression.Compile());
149         }
150  
151         /// <summary>
152         /// Removes any sort applied with <see cref="M:System.ComponentModel.BindingList`1.ApplySortCore(System.ComponentModel.PropertyDescriptor,System.ComponentModel.ListSortDirection)"/> if sorting is implemented in a derived class; otherwise, raises <see cref="T:System.NotSupportedException"/>.
153         /// </summary>
154         /// <exception cref="T:System.NotSupportedException">Method is not overridden in a derived class. </exception>
155         protected override void RemoveSortCore()
156         {
157             ResetItems(originalList);
158         }
159  
160         private void ResetItems(List<T> items)
161         {
162  
163             base.ClearItems();
164  
165             for (int i = 0; i < items.Count; i++)
166             {
167                 base.InsertItem(i, items[i]);
168             }
169         }
170  
171         /// <summary>
172         /// Gets a value indicating whether the list supports sorting.
173         /// </summary>
174         /// <value></value>
175         /// <returns>true if the list supports sorting; otherwise, false. The default is false.</returns>
176         protected override bool SupportsSortingCore
177         {
178             get
179             {
180                 // indeed we do
181                 return true;
182             }
183         }
184  
185         /// <summary>
186         /// Gets the direction the list is sorted.
187         /// </summary>
188         /// <value></value>
189         /// <returns>One of the <see cref="T:System.ComponentModel.ListSortDirection"/> values. The default is <see cref="F:System.ComponentModel.ListSortDirection.Ascending"/>. </returns>
190         protected override ListSortDirection SortDirectionCore
191         {
192             get
193             {
194                 return sortDirection;
195             }
196         }
197  
198         /// <summary>
199         /// Gets the property descriptor that is used for sorting the list if sorting is implemented in a derived class; otherwise, returns null.
200         /// </summary>
201         /// <value></value>
202         /// <returns>The <see cref="T:System.ComponentModel.PropertyDescriptor"/> used for sorting the list.</returns>
203         protected override PropertyDescriptor SortPropertyCore
204         {
205             get
206             {
207                 return sortProperty;
208             }
209         }
210  
211         /// <summary>
212         /// Raises the <see cref="E:System.ComponentModel.BindingList`1.ListChanged"/> event.
213         /// </summary>
214         /// <param name="e">A <see cref="T:System.ComponentModel.ListChangedEventArgs"/> that contains the event data.</param>
215         protected override void OnListChanged(ListChangedEventArgs e)
216         {
217             originalList = base.Items.ToList();
218         }
219     }
220 }

You can download the code and see the original sample.

Implementation was a snap:

1 List<myObject> MyList = DataLayer.GetMyList();
2  
3 myDataGridView.DataSource = new SortableBindingList<myObject>(MyList);