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(); } } |
可以这样做
1 | list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) ); |
.NET框架将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); } |
现在,您可以指定在调用
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(); } } |
笔记
如果类型实现了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 |
答案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 |