【问题标题】:Sorting a list using Lambda/Linq to objects使用 Lambda/Linq 对列表进行排序到对象
【发布时间】:2010-10-17 21:30:35
【问题描述】:

我在字符串中有“按属性排序”的名称。我将需要使用 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. 不是使用一堆 if 来检查字段名 (sortBy),有没有更简洁的排序方式
  2. 排序是否知道数据类型?

【问题讨论】:

  • 我看到了 sortBy == "FirstName"。 OP是否打算改为 .Equals()
  • @Pieter 他可能确实想比较平等,但我怀疑他“打算做 .Equals()”。错字通常不会导致代码起作用。
  • @Pieter 你的问题只有在你认为== 有问题时才有意义......什么?

标签: c# linq lambda linq-to-objects


【解决方案1】:

可以这样做

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

.NET 框架将 lambda (emp1,emp2)=&gt;int 转换为 Comparer&lt;Employee&gt;.

这具有强类型的优点。

如果您需要降序/倒序,请反转参数。

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

【讨论】:

  • 我经常遇到编写复杂的比较运算符,涉及多个比较条件和最后的故障保护 GUID 比较以确保反对称。你会使用 lambda 表达式来进行这样的复杂比较吗?如果不是,这是否意味着 lambda 表达式比较应该仅限于简单的情况?
  • 是的,我也没有看到类似的东西? list.Sort((emp1,emp2)=>emp1.GetType().GetProperty(sortBy).GetValue(emp1,null).CompareTo(emp2.​​GetType().GetProperty(sortBy).GetValue(emp2,null))) ;
  • 如何反向排序?
  • @JerryGoyal 交换参数... emp2.​​FirstName.CompareTo(emp1.FirstName) 等
  • 仅仅因为它是一个函数引用,它不一定是一个单行。你可以写list.sort(functionDeclaredElsewhere)
【解决方案2】:

您可以做的一件事是更改 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);

【讨论】:

  • 由于排序列在一个字符串中,你仍然需要一个 switch/if-else 块来确定传​​递哪个函数。
  • 你不能做出这样的假设。谁知道他的代码怎么称呼它。
  • 他在问题中表示“按属性排序”在字符串中。我只是在回答他的问题。
  • 我认为这更有可能是因为它来自网页上的排序控件,该控件将排序列作为字符串参数传回。无论如何,这将是我的用例。
  • @tvanfosson - 你是对的,我有一个自定义控件,其中包含顺序和字段名称作为字符串
【解决方案3】:

您可以使用反射来获取属性的值。

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 library 的动态 LINQ。您可以使用 IEnumerable 扩展将 List 转换为 IQueryable,然后使用动态链接 OrderBy 扩展。

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

【讨论】:

  • 虽然这确实解决了他的问题,但我们可能希望避免他使用字符串对其进行排序。很好的答案。
  • 你可以在没有 Linq to Sql 的情况下使用 Dynamic linq 来做他需要的事情......我喜欢它
  • 当然。您可以将其转换为 IQueryable。没有考虑到这一点。更新我的答案。
  • @Samuel 如果排序是作为路由变量传入的,则没有其他方法可以对其进行排序。
  • @ChuckD - 在尝试使用之前将集合放入内存,例如collection.ToList().OrderBy(x =&gt; TypeHelper.GetPropertyValue( x, sortBy)).ToList();
【解决方案4】:

这就是我解决问题的方法:

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

【讨论】:

    【解决方案5】:

    通过表达式构建顺序可以阅读here

    无耻地从链接中的页面窃取:

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

    【讨论】:

    • 存在与此相关的问题:日期时间排序。
    • 还有复合类,即Person.Employer.CompanyName?
    • 我基本上在做同样的事情,这个答案解决了它。
    【解决方案6】:

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

    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. 在我的示例中 list == null 将导致 NullReferenceException,您应该抓住这种情况。

    【讨论】:

    • 有没有其他人注意到这是一个返回类型 void 但返回列表?
    • 至少没有人愿意修复它,而且我没有注意到它,因为我没有使用 IDE 编写代码。感谢您指出这一点。
    【解决方案7】:

    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 的一种方法,不是 Linq 扩展。
    【解决方案8】:

    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.Default 将用于比较,如果您没有明确定义比较器。

    【讨论】:

    • 您有构建表达式树以传递给 OrderBy 的示例吗?
    【解决方案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();
        }
    
    }
    

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

    【讨论】:

      【解决方案10】:

      很遗憾,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!
      【解决方案11】:

      添加到 @Samuel 和 @bluish 所做的事情。这要短得多,因为在这种情况下 Enum 是不必要的。另外,当 Ascending 是所需结果时,作为额外的奖励,您只能传递 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);
      }
      

      【讨论】:

        【解决方案12】:

        如果您将排序列名称和排序方向作为字符串,并且不想使用 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;
            }
        

        基于使用通过表达式连接排序列所需的字典的解决方案>及其键字符串。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多