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
Sorting a list using Lambda/Linq 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();
}
}
- Instead of using a bunch of ifs to check the fieldname (sortBy), is there a cleaner way of doing the sorting
- Is sort aware of datatype?
- 不是使用一堆ifs来检查字段名(sortBy),而是有一种更清晰的排序方式
- 排序知道数据类型吗?
回答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 Sort
so 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 Sort
method.
现在您可以在调用该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
笔记
- Why do you pass the list by reference?
- You should use a enum for the sort direction.
- 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.
- In my example list == null will cause a NullReferenceException, you should catch this case.
- 为什么要通过引用传递列表?
- 您应该使用枚举作为排序方向。
- 如果您将指定要排序的属性而不是属性名称作为字符串传递的 lambda 表达式,您可以获得更清晰的解决方案。
- 在我的示例中 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)=>int
as a Comparer<Employee>.
.NET 框架将 lambda 转换(emp1,emp2)=>int
为Comparer<Employee>.
This has the advantage of being strongly typed.
这具有强类型的优点。