使用Lambda / Linq将对象列表sorting
我有一个string中的“按属性sorting”的名称。 我将需要使用Lambda / Linq来sorting对象列表。
例如:
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),是否有更清晰的sorting方法
- sorting意识到数据types?
这可以做到
list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );
.NET框架将lambda (emp1,emp2)=>int
作为比较器Comparer<Employee>.
投射Comparer<Employee>.
这具有强types的优点。
你可以做的一件事是改变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
方法时指定要sorting的字段。
Sort(ref employees, e => e.DOB, SortDirection.Descending);
您可以使用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示例库中查看dynamicLINQ。 您可以使用IEnumerable扩展将List转换为IQueryable,然后使用dynamic链接OrderBy扩展。
list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
通过expression式构build顺序可以在这里阅读
无耻地从链接页面中窃取:
// 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();
这是我解决我的问题的方法:
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(); }
您可以使用reflection来访问该属性。
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(); } }
笔记
- 你为什么通过引用通过名单?
- 你应该使用枚举的sorting方向。
- 如果您要传递一个lambdaexpression式来指定属性,而不是将属性名称作为string进行sorting,则可以获得更简洁的解决scheme。
- 在我的例子列表== null将导致一个NullReferenceException,你应该赶上这种情况。
sorting使用IComparable接口,如果types实现它。 你可以通过实现一个自定义的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));
答1:
您应该能够手动构build一个expression式树,可以使用该名称作为string传入OrderBy。 或者你可以使用reflectionbuild议在另一个答案,这可能是较less的工作。
编辑 :这是一个手动构buildexpression式树的工作示例。 (仅在知道属性名称“Value”时才在X.Value上sorting)。 你可以(应该)build立一个通用的方法来做到这一点。
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; } } }
但是,构buildexpression式树要求您知道参与types。 这可能会或可能不会成为您的使用情况中的问题。 如果你不知道你应该sorting什么types,使用reflection会更容易。
2的答案:
是的,因为比较器<T> .Default将用于比较,如果你没有明确定义比较器。
不幸的是Rashack提供的解决scheme不适用于值types(int,枚举等)。
对于任何types的财产,这是我find的解决scheme:
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; }
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))); } }
您可以传递多个sorting条件,如下所示:
var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });