Web developers wiki ASP.NET Sitecore Sharepoint Kentico by Evident Interactive

Generic list sort function

Modified: 2008/08/20 09:54 by admin - Categorized as: ASP.NET, Csharp
Edit

Introduction

In this article I will talk about how to sort the generic .NET list using reflection and extension methods in C#. This extension method for the generic list is able to sort a list on multiple properties. The usage is simply text-based, for example:

userlist.Sort("Firstname asc, Birthday desc");

Edit

Background

Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type. For client code written in C# and Visual Basic, there is no apparent difference between calling an extension method and the methods that are actually defined in a type.

Edit

Code

namespace Tools
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Linq.Expressions;
	using System.Reflection;
	using System.Text.RegularExpressions;

	public static class ListSorter
	{
		// Thanks to ubernostrum.wordpress.com/2008/08/01/updated-multi-property-sorting-with-linq/

		public enum SortingOrder
		{
			Ascending,
			Descending,
		};

		/// <summary>
		/// Sorts the specified generic list.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="list">The list.</param>
		/// <param name="sortOptions">The sort options, e.g.: "Name ASC, DateOfBirth DESCENDING".</param>
		public static void Sort<T>(this List<T> list, string sortOptions)
		{
			List<T> temp = ListSorter.Sort((IEnumerable<T>)list, sortOptions).ToList();
			list.Clear();
			list.AddRange(temp);
		}

		/// <summary>
		/// Sorts the specified IEnumerable.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="toSort">To sort.</param>
		/// <param name="sortOptions">The sort options, e.g.: "Name ASC, DateOfBirth DESCENDING".</param>
		/// <returns>The ordered enumerable.</returns>
		public static IOrderedEnumerable<T> Sort<T>(this IEnumerable<T> toSort, string sortOptions)
		{
			Dictionary<string, SortingOrder> dic = new Dictionary<string, SortingOrder>();
			MatchCollection matches = Regex.Matches(sortOptions, @"(?<=^|,) (\s*) (?<FIELD>.+?) (\s+) (?<ORDER>asc|desc) (ending)* (\s*) (?=,|$)", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture);
			if (matches.Count > 0)
			{
				foreach (Match match in matches)
				{
					string field = match.Groups"FIELD".Value;

					if (!dic.ContainsKey(field))
					{
						dic.Add(field, (match.Groups"ORDER".Value.ToLower() == "asc" ? SortingOrder.Ascending : SortingOrder.Descending));
					}
				}
			}
			return Sort(toSort, dic);
		}

		/// <summary>
		/// Sorts the specified IEnumerable.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="toSort">To sort.</param>
		/// <param name="sortOptions">The sort options.</param>
		/// <returns>The ordered enumerable.</returns>
		public static IOrderedEnumerable<T> Sort<T>(this IEnumerable<T> toSort, IDictionary<string, SortingOrder> sortOptions)
		{
			IOrderedEnumerable<T> orderedList = null;

			if (sortOptions != null && sortOptions.Count > 0)
			{
				//Get the primary sort option and remove it from the list
				//We will later check if there are additional elements in the list for further sorting
				KeyValuePair<string, SortingOrder> primarySort = sortOptions.ElementAt(0);
				sortOptions.Remove(primarySort);

				orderedList = primarySort.Value == SortingOrder.Ascending ? toSort.ApplyOrder(primarySort.Key, "OrderBy") : toSort.ApplyOrder(primarySort.Key, "OrderByDescending");

				//Continue the sort if there are more options
				if (sortOptions.Count > 0)
				{
					orderedList = orderedList.ContinueSort(sortOptions);
				}
			}

			return orderedList;
		}

		private static IOrderedEnumerable<T> ContinueSort<T>(this IOrderedEnumerable<T> orderedList, IDictionary<string, SortingOrder> sortOptions)
		{
			if (sortOptions != null)
			{
				foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
				{
					orderedList = entry.Value == SortingOrder.Ascending ? orderedList.ApplyOrder(entry.Key, "ThenBy") : orderedList.ApplyOrder(entry.Key, "ThenByDescending");
				}
			}

			return orderedList;
		}

		private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
		{
			ParameterExpression param = Expression.Parameter(typeof(T), "x");
			Expression expr = param;

			//Create the right-hand part of the lambda expression based on the property provided
			foreach (string prop in property.Split('.'))
			{
				expr = Expression.PropertyOrField(expr, prop);
			}

			Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
			LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);

			//Fetch the desired method
			MethodInfo mi = typeof(Enumerable).GetMethods().Single(method =>
				method.Name == methodName
				&& method.IsGenericMethodDefinition
				&& method.GetGenericArguments().Length == 2	// <TSource, TKey>
				&& method.GetParameters().Length == 2)		// source, keySelector
				.MakeGenericMethod(typeof(T), expr.Type);	// Substitute the appropriate types

			return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
		}
	}
} 

 © Evident Interactive BV