orderby - ordenar lista c# order by
Ordenar una lista usando Lambda/Linq a objetos (10)
Así es como resolví mi problema:
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();
}
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<Employee> list, string sortBy, string sortDirection)
{
//Example data:
//sortBy = "FirstName"
//sortDirection = "ASC" or "DESC"
if (sortBy == "FirstName")
{
list = list.OrderBy(x => x.FirstName).toList();
}
}
- En lugar de usar un montón de ifs para verificar el nombre de campo (sortBy), ¿hay una forma más limpia de hacer la clasificación?
- ¿Es el tipo de tipo de datos consciente?
Esto se puede hacer como
list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );
El marco .NET está lanzando la lambda (emp1,emp2)=>int
como un Comparer<Employee>.
Esto tiene la ventaja de ser fuertemente tipado.
La construcción del orden por expresión se puede leer here
Robado descaradamente 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<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);
// Now I can sort my people list.
Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();
La solución provista por Rashack no funciona para 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<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;
}
Ordenar utiliza la interfaz de IComparable, si el tipo la implementa. Y puede evitar los ifs implementando un IComparer personalizado:
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
}
}
y entonces
list.Sort(new EmpComp(sortBy));
Podría 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 );
}
}
Es posible que también desee ver Dynamic LINQ en la biblioteca de ejemplos de VS2008 . Podría usar la extensión IEnumerable para convertir la Lista como un IQueryable y luego usar la extensión OrderBy del enlace dinámico.
list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
Una cosa que podría hacer es cambiar la Sort
para que haga un mejor uso de las 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);
}
Ahora puede especificar el campo para ordenar al llamar al método de Sort
.
Sort(ref employees, e => e.DOB, SortDirection.Descending);
Usted podría utilizar la reflexión para acceder a la propiedad.
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();
}
}
Notas
- ¿Por qué pasa la lista por referencia?
- Debe utilizar una enumeración para la dirección de ordenación.
- Podría obtener una solución mucho más limpia si pasara una expresión lambda especificando la propiedad por la que se ordenará en lugar del nombre de la propiedad como una cadena.
- En mi lista de ejemplos == null causará una NullReferenceException, debe detectar este caso.
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, lo que podría ser menos trabajo.
Editar : Aquí hay un ejemplo práctico de cómo construir un árbol de expresiones manualmente. (Clasificación en X.Value, cuando solo se conoce el nombre "Valor" de la propiedad). Podrías (deberías) 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<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; }
}
}
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 en qué tipo debería clasificar, probablemente será más fácil utilizar la reflexión.
Respuesta para 2 .:
Sí, ya que Comparer <T>. Se utilizará el valor predeterminado para la comparación, si no define explícitamente el 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<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();
}
}
Otra, 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<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)));
}
}
Puede pasar varios criterios de clasificación, como este:
var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });