Ordenando una lista usando Lambda / Linq para objetos

Tengo el nombre de “ordenar por propiedad” en una cadena. Necesitaré usar Lambda / Linq para ordenar la lista de objetos.

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. En lugar de utilizar un montón de ifs para verificar el nombre de campo (sortBy), ¿hay una manera más clara de hacer la clasificación?
  2. ¿El tipo de datos es consciente del tipo de datos?

Esto se puede hacer como

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

El framework .NET está lanzando el lambda (emp1,emp2)=>int como Comparer.

Esto tiene la ventaja de estar fuertemente tipado.

Una cosa que podrías hacer es cambiar Sort para hacer un mejor uso de 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); } 

Ahora puede especificar el campo para clasificar al llamar al método Sort .

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

Puede usar Reflection para obtener el valor de la propiedad.

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

Donde TypeHelper tiene un 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 ); } } 

También es posible que desee ver Dynamic LINQ de la biblioteca de ejemplos de VS2008 . Puede usar la extensión IEnumerable para convertir la Lista como IQueryable y luego usar la extensión Dynamic Order OrderBy.

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

Así es como resolví mi 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(); } 

Creando el orden por expresión se puede leer aquí

Desvergonzadamente robado de la página en el enlace:

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

Podría usar el reflection para acceder a la propiedad.

 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 qué pasas la lista por referencia?
  2. Deberías usar una enumeración para la dirección de clasificación.
  3. Podría obtener una solución mucho más limpia si pasara una expresión lambda especificando la propiedad para ordenar por en lugar del nombre de la propiedad como una cadena.
  4. En mi lista de ejemplo == null causará una NullReferenceException, debería atrapar este caso.

Sort utiliza la interfaz IComparable, si el tipo lo implementa. Y puede evitar los ifs implementando un 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 } } 

y entonces

 list.Sort(new EmpComp(sortBy)); 

Respuesta para 1 .:

Debería poder construir manualmente un árbol de expresiones que se pueda pasar a OrderBy usando el nombre como una cadena. O podría usar la reflexión como se sugiere en otra respuesta, que podría ser menos trabajo.

Editar : Aquí hay un ejemplo de trabajo de construir un árbol de expresiones manualmente. (Clasificando en X.Value, cuando solo se conoce el nombre “Valor” de la propiedad). Podría (debería) construir un método genérico para hacerlo.

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

Sin embargo, construir un árbol de expresiones requiere que conozcas los tipos de particpación. Eso podría o no ser un problema en su escenario de uso. Si no sabe de qué tipo debe ordenar, será mucho más fácil usar la reflexión.

Respuesta para 2 .:

Sí, ya que Comparer .Default se usará para la comparación, si no define explícitamente al 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(); } } 

Otro, esta vez para cualquier 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))); } } 

Puede pasar múltiples criterios de clasificación, como este:

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

La solución provista por Rashack no funciona para los tipos de valor (int, enums, etc.) desafortunadamente.

Para que funcione con cualquier tipo de propiedad, esta es la solución que encontré:

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