Classificando uma lista usando Lambda / Linq para objects

Eu tenho o nome de “classificar por propriedade” em uma string. Eu precisarei usar o Lambda / Linq para ordenar a lista de objects.

Ex:

public class Employee { public string FirstName {set; get;} public string LastName {set; get;} public DateTime DOB {set; get;} } public void Sort(ref List list, string sortBy, string sortDirection) { //Example data: //sortBy = "FirstName" //sortDirection = "ASC" or "DESC" if (sortBy == "FirstName") { list = list.OrderBy(x => x.FirstName).toList(); } } 
  1. Em vez de usar um monte de ifs para verificar o nome do campo (sortBy), existe uma maneira mais limpa de fazer a sorting
  2. Está ciente do tipo de dados?

Isso pode ser feito

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

O framework .NET está lançando o lambda (emp1,emp2)=>int como Comparer.

Isso tem a vantagem de ser fortemente tipado.

Uma coisa que você pode fazer é mudar o Sort então faz um melhor uso dos lambdas.

 public enum SortDirection { Ascending, Descending } public void Sort(ref List list, Func sorter, SortDirection direction) { if (direction == SortDirection.Ascending) list = list.OrderBy(sorter); else list = list.OrderByDescending(sorter); } 

Agora você pode especificar o campo para classificar ao chamar o método Sort .

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

Você poderia usar o Reflection para obter o valor da propriedade.

 list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) ) .ToList(); 

Onde TypeHelper tem um método estático como:

 public static class TypeHelper { public static object GetPropertyValue( object obj, string name ) { return obj == null ? null : obj.GetType() .GetProperty( name ) .GetValue( obj, null ); } } 

Você também pode querer olhar para Dynamic LINQ da biblioteca de exemplos VS2008 . Você pode usar a extensão IEnumerable para converter a List como um IQueryable e, em seguida, usar a extensão OrderBy do link dynamic.

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

Foi assim que resolvi meu problema:

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

Construir a ordem por expressão pode ser lido aqui

Furiosamente roubado da página no 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>(Expression.Property(param, "DateOfBirth"), param); // Now I can sort my people list. Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray(); 

Você poderia usar reflection para acessar a propriedade.

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

Notas

  1. Por que você passa a lista por referência?
  2. Você deve usar um enum para a direção de sorting.
  3. Você poderia obter uma solução muito mais limpa se passasse uma expressão lambda especificando a propriedade a ser classificada em vez do nome da propriedade como uma string.
  4. Na minha lista de exemplo == null irá causar um NullReferenceException, você deve pegar este caso.

Sort usa a interface IComparable, se o tipo implementa. E você pode evitar os ifs implementando um IComparer personalizado:

 class EmpComp : IComparer { string fieldName; public EmpComp(string fieldName) { this.fieldName = fieldName; } public int Compare(Employee x, Employee y) { // compare x.fieldName and y.fieldName } } 

e depois

 list.Sort(new EmpComp(sortBy)); 

Resposta para 1 .:

Você deve ser capaz de criar manualmente uma tree de expressão que possa ser passada para OrderBy usando o nome como uma string. Ou você poderia usar a reflection como sugerido em outra resposta, o que pode ser menos trabalho.

Edit : Aqui está um exemplo de trabalho de construção de uma tree de expressão manualmente. (Classificando em X.Value, quando apenas se conhece o nome “Valor” da propriedade). Você poderia (deveria) construir um método genérico para fazê-lo.

 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>(expression, pe).Compile(); foreach (var n in randX.OrderBy(exp)) Console.WriteLine(n.Value); } public class X { public int Value { get; set; } } } 

Construir uma tree de expressão requer que você conheça os tipos de particionamento, no entanto. Isso pode ou não ser um problema no seu cenário de uso. Se você não sabe em que tipo você deve estar classificando, provavelmente será mais fácil usar a reflection.

Resposta para 2 .:

Sim, desde que o Comparer .Default será usado para a comparação, se você não definir explicitamente o comparador.

 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 OrderBy(this IEnumerable 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), "source"); return Expression.Lambda, IOrderedEnumerable>> ( 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 OrderBy(this IEnumerable source, string propertyName, bool ascending) { return ascending ? source.OrderBy(propertyName) : source.OrderBy(propertyName).Reverse(); } } 

Outro, desta vez para qualquer 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 OrderBy(this IQueryable source, params string[] sortDescriptors) { return sortDescriptors.Length > 0 ? source.OrderBy(sortDescriptors, 0) : source; } static IQueryable OrderBy(this IQueryable 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(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))); } } 

Você pode passar vários critérios de sorting, como este:

 var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" }); 

A solução fornecida pelo Rashack não funciona para tipos de valor (int, enums, etc.) infelizmente.

Para que funcione com qualquer tipo de propriedade, esta é a solução que encontrei:

 public static Expression> GetLambdaExpressionFor(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>(convertedBody, new[] { parameterExpression }); return expression; }