使用Lambda / Linq对对象排序列表


274

我在字符串中有“按属性排序”的名称。我将需要使用Lambda / Linq对对象列表进行排序。

例如:

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. 与其使用大量的ifs来检查字段名(sortBy),还没有一种更干净的方式来进行排序
  2. 是否了解数据类型?


我看到sortBy ==“ FirstName”。OP是否打算做.Equals()代替?
彼得2014年

3
@Pieter他可能确实想比较平等,但我怀疑他“想做.Equals()”。错别字通常不会导致代码正常工作。
C.Evenhuis 2014年

1
@Pieter您的问题只有在您认为==...出了什么问题时才有意义。
吉姆·巴尔特

Answers:


365

可以这样做

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

.NET框架将lambda转换(emp1,emp2)=>intComparer<Employee>.

这具有强类型输入的优势。


我经常碰巧编写复杂的比较运算符,其中涉及多个比较标准以及最终要确保反对称性的故障安全GUID比较。您会使用lambda表达式进行类似的复杂比较吗?如果不是,这是否意味着lambda表达式比较应仅限于简单情况?
西蒙妮

4
是的,我看不到像这样的东西?list.Sort((emp1,emp2)=> emp1.GetType()。GetProperty(sortBy).GetValue(emp1,null).CompareTo(emp2.​​GetType()。GetProperty(sortBy).GetValue(emp2,null)))) ;
2014年

1
如何反向排序?
JerryGoyal '16

1
@JerryGoyal交换参数... emp2.​​FirstName.CompareTo(emp1.FirstName)等
克里斯·海因斯

3
仅仅因为它是一个函数引用,它就不必是一个内衬。您可以撰写list.sort(functionDeclaredElsewhere)
The Hoff

74

您可以做的一件事就是更改,Sort以便更好地利用lambda。

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

7
由于sort列在字符串中,因此您仍然需要一个switch / if-else块来确定将其传递给哪个函数。
tvanfosson

1
你不能做那个假设。谁知道他的代码如何调用它。
塞缪尔'2009年

3
他在问题中说“按财产分类”是一个字符串。我只是按他的问题。
tvanfosson

6
我认为它更有可能是因为它来自网页上的排序控件,该控件将sort列作为字符串参数传递回。无论如何,那将是我的用例。
tvanfosson

2
@tvanfosson-是的,我有一个自定义控件,该控件以顺序和字段名作为字符串
DotnetDude

55

您可以使用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 );
    }
}

您可能还需要查看VS2008 Samples库中的 Dynamic LINQ 。您可以使用IEnumerable扩展名将列表转换为IQueryable,然后使用动态链接OrderBy扩展名。

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

1
尽管这确实解决了他的问题,但我们可能希望避免他使用字符串对它进行排序。好的答案还是。
塞缪尔2009年

您可以在不使用Linq到Sql的情况下使用Dynamic linq来执行他所需要的...我喜欢它
JoshBerke

当然。您可以将其转换为IQueryable。没想到。更新我的答案。
tvanfosson

@Samuel如果将排序作为路由变量传入,则没有其他方法可以对其进行排序。
2012年

1
@ChuckD-在尝试使用收藏之前将其带入内存,例如collection.ToList().OrderBy(x => TypeHelper.GetPropertyValue( x, sortBy)).ToList();
tvanfosson

20

这是我解决问题的方式:

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

16

通过表达式建立订单可以在这里阅读

从页面的链接中无耻地被盗:

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

有与此相关的问题:DateTime排序。
CrazyEnigma

还有复合类,即Person.Employer.CompanyName呢?
davewilliams459 2011年

我基本上在做同样的事情,这个答案解决了它。
Jason.Net 2012年

8

您可以使用反射来访问属性。

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. 如果传递一个lambda表达式来指定要排序的属性,而不是将属性名称作为字符串传递,则可以得到更简洁的解决方案。
  4. 在我的示例列表中,== null将导致NullReferenceException,您应该抓住这种情况。

有没有其他人注意到这是一个返回类型为void但返回列表?
2013年

至少没有人愿意修复它,而且我没有注意到它,因为我没有使用IDE编写代码。感谢您指出了这一点。
DanielBrückner2013年

6

如果类型实现,则Sort使用IComparable接口。您可以通过实现自定义IComparer来避免ifs:

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

仅供参考:Sort是List <T>的方法,而不是Linq扩展。
Serguei,2009年

5

回答1 .:

您应该能够手动构建一个表达式树,该树可以使用名称作为字符串传递到OrderBy中。或者您可以按照其他答案中的建议使用反射,这可能会减少工作量。

编辑:这是一个手动构建表达式树的工作示例。(仅当知道属性的名称“ Value”时,对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 <T> .Default进行比较。


您是否有构建传递给OrderBy的表达式树的示例?
DotnetDude

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

}

另一个,这次用于任何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" });

4

不幸的是,Rashack提供的解决方案不适用于值类型(int,枚举等)。

为了使其能够与任何类型的属性一起使用,这是我发现的解决方案:

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

这很棒,甚至可以正确地转换为SQL!
Xavier Poinas

1

添加到@Samuel和@bluish所做的事情。这要短得多,因为在这种情况下不需要枚举。另外,当升序是期望的结果时,作为附加奖励,您只能传递2个参数而不是3个参数,因为true是第三个参数的默认答案。

public void Sort<TKey>(ref List<Person> list, Func<Person, TKey> sorter, bool isAscending = true)
{
    list = isAscending ? list.OrderBy(sorter) : list.OrderByDescending(sorter);
}

0

如果您将排序列名称和排序方向作为字符串获取,并且不想使用switch或if \ else语法来确定列,那么此示例可能对您很有趣:

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

基于使用Dictionary的解决方案,该字典通过Expression>及其键字符串连接排序列所需的内容。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.