280

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?
Jim Balter
  • 15,014
  • 3
  • 38
  • 62
DotnetDude
  • 11,119
  • 30
  • 94
  • 156
  • 3
    Dupe: http://stackoverflow.com/questions/606997/linq-order-by-for-a-listof-myobjects – mmx Apr 06 '09 at 19:50
  • I see *sortBy == "FirstName"*. Did the OP mean to do *.Equals()* instead? – Pieter May 07 '14 at 13:27
  • 3
    @Pieter he probably did mean to compare equality, but I doubt he "meant to do .Equals()". Typo's usually don't result in code that functions. – C.Evenhuis Jul 21 '14 at 13:53
  • 1
    @Pieter Your question only makes sense if you think there's something wrong with `==` ... what? – Jim Balter Dec 27 '16 at 07:23

12 Answers12

381

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.

If you need the descending/reverse order invert the parameters.

list.Sort( (emp1,emp2)=>emp2.FirstName.CompareTo(emp1.FirstName) );
gls123
  • 5,067
  • 2
  • 25
  • 27
  • It often happened to me to write complex comparison operators, involving multiple comparison criteria and a failsafe GUID comparison in the end to ensure antisymmetry. Would you use a lambda expression for a complex comparison like that? If not, does this mean that lambda expression comparisons should only be limited to simple cases? – Simone Nov 05 '13 at 09:04
  • The questions says the property is "in a string" – Quinn Wilson Nov 25 '13 at 20:16
  • 4
    yeah i dont see it either something like this? list.Sort( (emp1,emp2)=>emp1.GetType().GetProperty(sortBy).GetValue(emp1,null).CompareTo(emp2.GetType().GetProperty(sortBy).GetValue(emp2,null)) ); – Sat Jan 02 '14 at 07:38
  • So, I tried that, but I'm getting "Cannot convert lambda expression to type 'System.Collections.Generic.IComparer' because it is not a delegate type." My code: _queries.Sort((item1, item2) => item.Name.Equals(item2.Name)); – Ari Roth Jul 02 '14 at 00:36
  • For the record, I got around the error message via using an explicit instance of Comparer, but I'm curious as to what went wrong. – Ari Roth Jul 02 '14 at 00:43
  • Two years late for Ari Roth but for anyone wondering, you need to use .CompareTo(...), not .Equals(...). – Elisée Aug 20 '16 at 06:00
  • 2
    how to sort in reverse? – GorvGoyl Oct 20 '16 at 09:40
  • 2
    @JerryGoyal swap the params... emp2.FirstName.CompareTo(emp1.FirstName) etc. – Chris Hynes Nov 29 '16 at 23:17
  • 3
    Just because it's a function reference doesn't it has to be a one liner. You could just write `list.sort(functionDeclaredElsewhere)` – The Hoff Oct 24 '18 at 06:46
75

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);
bluish
  • 23,093
  • 23
  • 110
  • 171
Samuel
  • 35,821
  • 11
  • 83
  • 84
  • 7
    Since the sort column is in a string you'd still need a switch/if-else blocks to determine which function to pass it. – tvanfosson Apr 06 '09 at 19:49
  • 1
    You can't make that assumption. Who knows how his code calls it. – Samuel Apr 06 '09 at 19:51
  • 3
    He stated in the question that the "sort by property" is in a string. I'm just going by his question. – tvanfosson Apr 06 '09 at 19:53
  • 1
    True, but maybe it's only in string form because he didn't know he could use lambdas to sort it like this? – Samuel Apr 06 '09 at 19:56
  • 6
    I think it's more likely because it's coming from a sort control on a web page that passes the sort column back as a string parameter. That would be my use case, anyway. – tvanfosson Apr 06 '09 at 20:31
  • 2
    @tvanfosson - You are right, I have a custom control that has the order and the field name as a string – DotnetDude Apr 06 '09 at 21:08
  • @tvanfosson "Since the sort column is in a string you'd still need a switch/if-else blocks to determine which function to pass it. " -- No, you don't ... you can have a Dictionary>. The OP asked for something cleaner, and this gets part way there. It should also get rid of that awful `ref` and return the sorted list (changing the name of the method from `Sort` to `Sorted`). – Jim Balter Dec 27 '16 at 07:37
56

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 );
tvanfosson
  • 490,224
  • 93
  • 683
  • 780
  • 1
    While this does solve his problem, we might want to steer him away from using a string to sort it. Good answer none-the-less. – Samuel Apr 06 '09 at 19:49
  • You can use Dynamic linq without Linq to Sql to do what he needs...I love it – JoshBerke Apr 06 '09 at 19:54
  • Sure. You can convert it to IQueryable. Didn't think about that. Updating my answer. – tvanfosson Apr 06 '09 at 19:59
  • @Samuel If the sort is coming in as a route variable there is no other way to sort it. – Chev Feb 08 '12 at 17:14
  • @AlexFord Yes there is. If the sort is coming in as a route variable you will get it as a string. You can generate a lambda expression for the property using this string. – Jerry Joseph May 16 '13 at 13:58
  • LINQ to Entities does not recognize the method GetPropertyValue method, and this method cannot be translated into a store expression – Chuck D Feb 01 '18 at 17:41
  • 1
    @ChuckD - bring the collection into memory before you attempt to use it, e.g. `collection.ToList().OrderBy(x => TypeHelper.GetPropertyValue( x, sortBy)).ToList();` – tvanfosson Feb 01 '18 at 17:55
20

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();
}
Dmitrii Lobanov
  • 4,697
  • 1
  • 31
  • 47
Cornel Urian
  • 366
  • 4
  • 8
16

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();
Rashack
  • 4,608
  • 2
  • 24
  • 34
8

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.
Daniel Brückner
  • 56,191
  • 15
  • 92
  • 137
  • Has anyone else ever noticed that this is a return type void but returns lists? – emd Jun 14 '13 at 18:53
  • At least no one cared to fix it and I did not noticed it because I did not write the code using an IDE. Thanks for pointing that out. – Daniel Brückner Jun 15 '13 at 01:43
6

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));
Samuel
  • 35,821
  • 11
  • 83
  • 84
Serguei
  • 2,833
  • 3
  • 22
  • 32
5

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.

driis
  • 151,614
  • 43
  • 262
  • 332
4
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" });
Andras Vass
  • 11,088
  • 1
  • 33
  • 47
4

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;
    }
jhdrn
  • 187
  • 1
  • 8
Antoine Jaussoin
  • 4,462
  • 4
  • 22
  • 37
1

Adding to what @Samuel and @bluish did. This is much shorter as the Enum was unnecessary in this case. Plus as an added bonus when the Ascending is the desired result, you can pass only 2 parameters instead of 3 since true is the default answer to the third parameter.

public void Sort<TKey>(ref List<Person> list, Func<Person, TKey> sorter, bool isAscending = true)
{
    list = isAscending ? list.OrderBy(sorter) : list.OrderByDescending(sorter);
}
SiKing
  • 8,745
  • 10
  • 35
  • 78
0

If you get sort column name and sort direction as string and don't want to use switch or if\else syntax to determine column, then this example may be interesting for you:

private readonly Dictionary<string, Expression<Func<IuInternetUsers, object>>> _sortColumns = 
        new Dictionary<string, Expression<Func<IuInternetUsers, object>>>()
    {
        { nameof(ContactSearchItem.Id),             c => c.Id },
        { nameof(ContactSearchItem.FirstName),      c => c.FirstName },
        { nameof(ContactSearchItem.LastName),       c => c.LastName },
        { nameof(ContactSearchItem.Organization),   c => c.Company.Company },
        { nameof(ContactSearchItem.CustomerCode),   c => c.Company.Code },
        { nameof(ContactSearchItem.Country),        c => c.CountryNavigation.Code },
        { nameof(ContactSearchItem.City),           c => c.City },
        { nameof(ContactSearchItem.ModifiedDate),   c => c.ModifiedDate },
    };

    private IQueryable<IuInternetUsers> SetUpSort(IQueryable<IuInternetUsers> contacts, string sort, string sortDir)
    {
        if (string.IsNullOrEmpty(sort))
        {
            sort = nameof(ContactSearchItem.Id);
        }

        _sortColumns.TryGetValue(sort, out var sortColumn);
        if (sortColumn == null)
        {
            sortColumn = c => c.Id;
        }

        if (string.IsNullOrEmpty(sortDir) || sortDir == SortDirections.AscendingSort)
        {
            contacts = contacts.OrderBy(sortColumn);
        }
        else
        {
            contacts = contacts.OrderByDescending(sortColumn);
        }

        return contacts;
    }

Solution based on using Dictionary that connects needed for sort column via Expression> and its key string.