Tutorial :Sorting a list using Lambda/Linq to objects



Question:

I have the name of the "sort by property" in a string. I will need to use Lambda/Linq to sort the list of objects.

Ex:

public class Employee  {    public string FirstName {set; get;}    public string LastName {set; get;}    public DateTime DOB {set; get;}  }      public void Sort(ref List<Employee> list, string sortBy, string sortDirection)  {    //Example data:    //sortBy = "FirstName"    //sortDirection = "ASC" or "DESC"      if (sortBy == "FirstName")    {      list = list.OrderBy(x => x.FirstName).toList();        }    }  
  1. Instead of using a bunch of ifs to check the fieldname (sortBy), is there a cleaner way of doing the sorting
  2. Is sort aware of datatype?


Solution:1

This can be done as

list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );  

The .NET framework is casting the lambda (emp1,emp2)=>int as a Comparer<Employee>.

This has the advantage of being strongly typed.


Solution:2

One thing you could do is change Sort so it makes better use of lambdas.

public enum SortDirection { Ascending, Descending }  public void Sort<TKey>(ref List<Employee> list,                         Func<Employee, TKey> sorter, SortDirection direction)  {    if (direction == SortDirection.Ascending)      list = list.OrderBy(sorter);    else      list = list.OrderByDescending(sorter);  }  

Now you can specify the field to sort when calling the Sort method.

Sort(ref employees, e => e.DOB, SortDirection.Descending);  


Solution:3

You could use Reflection to get the value of the property.

list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) )             .ToList();  

Where TypeHelper has a static method like:

public static class TypeHelper  {      public static object GetPropertyValue( object obj, string name )      {          return obj == null ? null : obj.GetType()                                         .GetProperty( name )                                         .GetValue( obj, null );      }  }  

You might also want to look at Dynamic LINQ from the VS2008 Samples library. You could use the IEnumerable extension to cast the List as an IQueryable and then use the Dynamic link OrderBy extension.

 list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );  


Solution:4

This is how I solved my problem:

List<User> list = GetAllUsers();  //Private Method    if (!sortAscending)  {      list = list             .OrderBy(r => r.GetType().GetProperty(sortBy).GetValue(r,null))             .ToList();  }  else  {      list = list             .OrderByDescending(r => r.GetType().GetProperty(sortBy).GetValue(r,null))             .ToList();  }  


Solution:5

Building the order by expression can be read here

Shamelessly stolen from the page in link:

// First we define the parameter that we are going to use  // in our OrderBy clause. This is the same as "(person =>"  // in the example above.  var param = Expression.Parameter(typeof(Person), "person");    // Now we'll make our lambda function that returns the  // "DateOfBirth" property by it's name.  var mySortExpression = Expression.Lambda<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);    // Now I can sort my people list.  Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();  


Solution:6

You could use reflection to access the property.

public List<Employee> Sort(List<Employee> list, String sortBy, String sortDirection)  {     PropertyInfo property = list.GetType().GetGenericArguments()[0].                                  GetType().GetProperty(sortBy);       if (sortDirection == "ASC")     {        return list.OrderBy(e => property.GetValue(e, null));     }     if (sortDirection == "DESC")     {        return list.OrderByDescending(e => property.GetValue(e, null));     }     else     {        throw new ArgumentOutOfRangeException();     }  }  

Notes

  1. Why do you pass the list by reference?
  2. You should use a enum for the sort direction.
  3. You could get a much cleaner solution if you would pass a lambda expression specifying the property to sort by instead of the property name as a string.
  4. In my example list == null will cause a NullReferenceException, you should catch this case.


Solution:7

Sort uses the IComparable interface, if the type implements it. And you can avoid the ifs by implementing a custom IComparer:

class EmpComp : IComparer<Employee>  {      string fieldName;      public EmpComp(string fieldName)      {          this.fieldName = fieldName;      }        public int Compare(Employee x, Employee y)      {          // compare x.fieldName and y.fieldName      }  }  

and then

list.Sort(new EmpComp(sortBy));  


Solution:8

Answer for 1.:

You should be able to manually build an expression tree that can be passed into OrderBy using the name as a string. Or you could use reflection as suggested in another answer, which might be less work.

Edit: Here is a working example of building an expression tree manually. (Sorting on X.Value, when only knowing the name "Value" of the property). You could (should) build a generic method for doing it.

using System;  using System.Linq;  using System.Linq.Expressions;    class Program  {      private static readonly Random rand = new Random();      static void Main(string[] args)      {          var randX = from n in Enumerable.Range(0, 100)                      select new X { Value = rand.Next(1000) };            ParameterExpression pe = Expression.Parameter(typeof(X), "value");          var expression = Expression.Property(pe, "Value");          var exp = Expression.Lambda<Func<X, int>>(expression, pe).Compile();            foreach (var n in randX.OrderBy(exp))              Console.WriteLine(n.Value);      }        public class X      {          public int Value { get; set; }      }  }  

Building an expression tree requires you to know the particpating types, however. That might or might not be a problem in your usage scenario. If you don't know what type you should be sorting on, it will propably be easier using reflection.

Answer for 2.:

Yes, since Comparer<T>.Default will be used for the comparison, if you do not explicitly define the comparer.


Solution:9

using System;  using System.Collections.Generic;  using System.Linq;  using System.Reflection;  using System.Linq.Expressions;    public static class EnumerableHelper  {        static MethodInfo orderBy = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();        public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName)      {          var pi = typeof(TSource).GetProperty(propertyName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);          var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");          var sourceParam = Expression.Parameter(typeof(IEnumerable<TSource>), "source");          return               Expression.Lambda<Func<IEnumerable<TSource>, IOrderedEnumerable<TSource>>>              (                  Expression.Call                  (                      orderBy.MakeGenericMethod(typeof(TSource), pi.PropertyType),                       sourceParam,                       Expression.Lambda                      (                          typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType),                           Expression.Property(selectorParam, pi),                           selectorParam                      )                  ),                   sourceParam              )              .Compile()(source);      }        public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName, bool ascending)      {          return ascending ? source.OrderBy(propertyName) : source.OrderBy(propertyName).Reverse();      }    }  

Another one, this time for any IQueryable:

using System;  using System.Linq;  using System.Linq.Expressions;  using System.Reflection;    public static class IQueryableHelper  {        static MethodInfo orderBy = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();      static MethodInfo orderByDescending = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderByDescending" && x.GetParameters().Length == 2).First();        public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, params string[] sortDescriptors)      {          return sortDescriptors.Length > 0 ? source.OrderBy(sortDescriptors, 0) : source;      }        static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string[] sortDescriptors, int index)      {          if (index < sortDescriptors.Length - 1) source = source.OrderBy(sortDescriptors, index + 1);          string[] splitted = sortDescriptors[index].Split(' ');          var pi = typeof(TSource).GetProperty(splitted[0], BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.IgnoreCase);          var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");          return source.Provider.CreateQuery<TSource>(Expression.Call((splitted.Length > 1 && string.Compare(splitted[1], "desc", StringComparison.Ordinal) == 0 ? orderByDescending : orderBy).MakeGenericMethod(typeof(TSource), pi.PropertyType), source.Expression, Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType), Expression.Property(selectorParam, pi), selectorParam)));      }    }  

You can pass multiple sort criteria, like this:

var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });  


Solution:10

The solution provided by Rashack does not work for value types (int, enums, etc.) unfortunately.

For it to work with any type of property, this is the solution I found:

public static Expression<Func<T, object>> GetLambdaExpressionFor<T>(this string sortColumn)      {          var type = typeof(T);          var parameterExpression = Expression.Parameter(type, "x");          var body = Expression.PropertyOrField(parameterExpression, sortColumn);          var convertedBody = Expression.MakeUnary(ExpressionType.Convert, body, typeof(object));            var expression = Expression.Lambda<Func<T, object>>(convertedBody, new[] { parameterExpression });            return expression;      }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »