10.5.2009

OrderBy().Descending()

Just wanted to share a quick extension method, it's really simple yes but it's power is in reducing lines of code. If you have ever wanted to apply an order by clause to a collection of items and conditionally do this based on a direction, you know that the only choices available are different methods OrderBy and OrderByDescending. It really is too bad because the internal OrderedEnumerable has a boolean flag for direction that would have been nice as a parameter, but better yet why not make it fluent while we are at it!

Normally we might have to write code like this, assuming I want to order by a particular key in a dictionary field on the item record. (This is just a sample of a nasty selector for ordering that we wouldn't want to be copy & pasting):

if(ascending)
{
  items = items.OrderBy(i => i.DictionaryField.Keys.Contains(key) ? i.DictionaryField[key] : null)
}
else
{
  items = items.OrderByDescending(i => i.DictionaryField.Keys.Contains(key) ? i.DictionaryField[key] : null)
}
The next refactoring might be, which might not be so bad if we could use var instead of Func..., but c# doesn't deal well with implicit functions because of the static typing thing!

Func<Item, object> nastySelector = i => i.DictionaryField.Keys.Contains(key) ? i.DictionaryField[key] : null;
if(ascending)
{
  items = items.OrderBy(nastySelector);
}
else
{
  items = items.OrderByDescending(nastySelector);
}
Next, I can combine the if/else using the ternary operator:

Func<Item, object> nastySelector = i => i.DictionaryField.Keys.Contains(key) ? i.DictionaryField[key] : null;
items = ascending ? items.OrderBy(nastySelector) : items.OrderByDescending(nastySelector);
This is pretty good, but it's not very readable. A chained Descending method (on OrderBy results) would be nice, ridding us of the Func declaration garble and even making our items assignment much more fluent!

ordered = items.OrderBy(i => i.DictionaryField.Keys.Contains(key) ? i.DictionaryField[key] : null);
items = ascending ? ordered : ordered.Descending();
So here is the Descending implementation, this is for Enumerable lists only, if you have an IQueryable collection, cast it to IEnumerable first. This makes use of reflection to set the internal field (descending) on the internal class that you are eternally not supposed to touch :).

public static IOrderedEnumerable<TSource> Descending<TSource>(this IOrderedEnumerable<TSource> source)
{
  var field = source.GetType().GetField("descending", BindingFlags.NonPublic | BindingFlags.Instance);
  if(field == null)
  {
    throw new ArgumentException("Source must be OrderedEnumerable");
  }

field.SetValue(source, true);

return source; }





comments powered by Disqus