C# 使用 Lambda/Linq to objects 对列表进行排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/722868/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 22:33:16  来源:igfitidea点击:

Sorting a list using Lambda/Linq to objects

c#linqlambdalinq-to-objects

提问by DotnetDude

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.

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

回答by driis

Answer for 1.:

1. 的答案:

You should be able to manually build an expression tree that can be passed into OrderBy using the name as a string. Oryou could use reflection as suggested in another answer, which might be less work.

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

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.

编辑:这是手动构建表达式树的工作示例。(当只知道属性的名称“值”时,按 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; }
    }
}

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.:

2. 答案:

Yes, since Comparer<T>.Default will be used for the comparison, if you do not explicitly define the comparer.

是的,因为 Comparer<T>.Default 将用于比较,如果您没有明确定义比较器。

回答by tvanfosson

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:

TypeHelper 有一个静态方法,如:

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.

您可能还想查看VS2008 示例库中的动态 LINQ 。您可以使用 IEnumerable 扩展将 List 转换为 IQueryable,然后使用动态链接 OrderBy 扩展。

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

回答by Samuel

One thing you could do is change Sortso it makes better use of lambdas.

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

Now you can specify the field to sort when calling the Sortmethod.

现在您可以在调用该Sort方法时指定要排序的字段。

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

回答by Serguei

Sort uses the IComparable interface, if the type implements it. And you can avoid the ifs by implementing a custom IComparer:

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

and then

进而

list.Sort(new EmpComp(sortBy));

回答by Daniel Brückner

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.
  1. 为什么要通过引用传递列表?
  2. 您应该使用枚举作为排序方向。
  3. 如果您将指定要排序的属性而不是属性名称作为字符串传递的 lambda 表达式,您可以获得更清晰的解决方案。
  4. 在我的示例中 list == null 将导致 NullReferenceException,您应该捕获这种情况。

回答by Rashack

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

回答by Andras Vass

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:

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

回答by Antoine Jaussoin

The solution provided by Rashack does not work for value types (int, enums, etc.) unfortunately.

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

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

回答by Cornel Urian

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

回答by gls123

This can be done as

这可以作为

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

The .NET framework is casting the lambda (emp1,emp2)=>intas a Comparer<Employee>.

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

This has the advantage of being strongly typed.

这具有强类型的优点。