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

我在字符串中有“按属性排序”的名称。我需要使用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. 排序是否知道数据类型?
538352 次浏览

回答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进行比较。

您可以使用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样本库中查看Dynamic LINQ。您可以使用IEnumerable扩展将List转换为IQueryable,然后使用Dynamic link OrderBy扩展。

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

你可以做的一件事是改变Sort,使它更好地使用lambdas。

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

Sort使用IComparable接口,如果该类型实现了它。 你可以通过实现一个自定义的IComparer来避免if:

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

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

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,您应该捕获这种情况。

通过表达式构建顺序可以读取在这里

无耻地窃取页面链接:

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

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

对于任何类型的属性,这是我发现的解决方案:

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

我是这样解决问题的:

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

这可以用

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

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

这具有强类型的优点。

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

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

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

如果你得到排序列名和排序方向为字符串,并且不想使用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的解决方案,通过表达式>和它的键字符串连接排序列所需的字典。