关于c#:使用Lambda / Linq对列表对列表进行排序

Sorting a list using Lambda/Linq to objects

我在字符串中有"按属性排序"的名称。我需要使用lambda/linq对对象列表进行排序。

前任:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();    
  }

}
  • 不是用一堆IFS来检查字段名(sortby),有没有更干净的排序方法
  • 排序是否知道数据类型?

  • 可以这样做

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

    .NET框架将lambda (emp1,emp2)=>int转换为Comparer.

    这具有强类型化的优点。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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方法时要排序的字段。

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


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

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

    其中,typehelper具有如下静态方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static class TypeHelper
    {
        public static object GetPropertyValue( object obj, string name )
        {
            return obj == null ? null : obj.GetType()
                                           .GetProperty( name )
                                           .GetValue( obj, null );
        }
    }

    您可能还需要查看VS2008示例库中的动态LINQ。您可以使用IEnumerable扩展将列表强制转换为iQuery,然后使用动态链接orderby扩展。

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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();
    }

    此处可以读取按顺序构建表达式

    不知羞耻地从链接中的页面中窃取:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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();


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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();
       }
    }

    笔记

  • 为什么你要通过引用列表?
  • 应该使用枚举作为排序方向。
  • 如果传递lambda表达式,则可以得到更干净的解决方案指定要排序的属性,而不是将属性名指定为字符串。
  • 在我的示例列表中,==null将导致NullReferenceException,您应该注意这个情况。

  • 如果类型实现了IComparable接口,sort将使用该接口。您可以通过实现自定义的IComparer来避免IFS:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
        }
    }

    然后

    1
    list.Sort(new EmpComp(sortBy));


    答案1:

    您应该能够手动构建一个表达式树,该树可以使用名称作为字符串传递给order。或者您可以使用反射,如另一个答案中建议的那样,这可能会减少工作。

    编辑:这里有一个手工构建表达式树的工作示例。(当只知道属性的名称"value"时,对x.value进行排序)。您可以(应该)构建一个通用的方法来实现它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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默认值。


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

    为了让它与任何类型的属性一起工作,我发现了以下解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
        }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    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();
        }

    }

    另一个,这一次对于任何iquerier:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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)));
        }

    }

    您可以传递多个排序条件,如下所示:

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