Сортировка списка с помощью Lambda / Linq по объектам - PullRequest
258 голосов
/ 06 апреля 2009

У меня есть имя «сортировать по свойству» в строке. Мне нужно будет использовать Lambda / Linq для сортировки списка объектов.

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. Вместо использования набора if для проверки имени поля (sortBy), существует ли более чистый способ сортировки
  2. Знает ли сортировка тип данных?

Ответы [ 10 ]

341 голосов
/ 02 января 2013

Это можно сделать как

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

.NET Framework использует лямбду (emp1,emp2)=>int как Comparer<Employee>.

Преимущество в том, что вы строго напечатаны.

73 голосов
/ 06 апреля 2009

Одна вещь, которую вы можете сделать, это изменить Sort, чтобы лучше использовать лямбды.

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);
}

Теперь вы можете указать поле для сортировки при вызове метода Sort.

Sort(ref employees, e => e.DOB, SortDirection.Descending);
54 голосов
/ 06 апреля 2009

Вы можете использовать Reflection, чтобы получить значение свойства.

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

Где TypeHelper имеет статический метод, такой как:

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

Вы также можете посмотреть на Dynamic LINQ из библиотеки VS2008 Samples . Вы можете использовать расширение IEnumerable, чтобы преобразовать List в IQueryable, а затем использовать динамическое расширение OrderBy.

 list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
18 голосов
/ 20 февраля 2012

Вот как я решил свою проблему:

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();
}
15 голосов
/ 06 апреля 2009

Построение порядка по выражению можно прочитать здесь

Бесстыдно похищен со страницы в ссылке:

// 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();
8 голосов
/ 06 апреля 2009

Вы можете использовать отражение для доступа к свойству.

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();
   }
}

Примечания

  1. Почему вы передаете список по ссылке?
  2. Вы должны использовать перечисление для направления сортировки.
  3. Вы могли бы получить намного более чистое решение, если бы передавали лямбда-выражение указание свойства для сортировки вместо имени свойства в виде строки.
  4. В моем примере списка == null вызовет исключение NullReferenceException, вы должны поймать этот случай.
6 голосов
/ 06 апреля 2009

Сортировка использует интерфейс IComparable, если тип реализует его. И вы можете избежать ifs, реализовав собственный 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
    }
}

, а затем

list.Sort(new EmpComp(sortBy));
5 голосов
/ 06 апреля 2009

Ответ за 1.:

Вы должны иметь возможность вручную построить дерево выражений, которое можно передать в OrderBy, используя имя в качестве строки. Или Вы можете использовать отражение, как предложено в другом ответе, что может быть меньше работы.

Редактировать : Вот рабочий пример построения дерева выражений вручную. (Сортировка по X.Value, когда известно только имя «Значение» свойства). Вы можете (должны) создать универсальный метод для этого.

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; }
    }
}

Однако для построения дерева выражений вам необходимо знать типы участников. Это может или не может быть проблемой в вашем сценарии использования. Если вы не знаете, по какому типу вы должны сортировать, возможно, будет легче использовать рефлексию.

Ответ за 2.:

Да, так как Comparer .Default будет использоваться для сравнения, если вы явно не определите его.

4 голосов
/ 07 марта 2011

Решение, предоставленное Rashack, к сожалению, не работает для типов значений (int, enums и т. Д.).

Чтобы оно работало с любым типом собственности, я нашел следующее решение:

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;
    }
4 голосов
/ 18 февраля 2010
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();
    }

}

Еще один, на этот раз для любого 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)));
    }

}

Вы можете передать несколько критериев сортировки, например:

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