Ordinamento di un elenco utilizzando Lambda / Linq agli oggetti

Ho il nome di “ordina per proprietà” in una stringa. Dovrò usare Lambda / Linq per ordinare l’elenco degli oggetti.

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. Invece di usare un gruppo di if per controllare il fieldname (sortBy), c’è un modo più pulito di fare l’ordinamento
  2. È consapevole del tipo di dati?

Questo può essere fatto come

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

Il framework .NET sta lanciando il lambda (emp1,emp2)=>int come un Comparer.

Questo ha il vantaggio di essere fortemente tipizzato.

Una cosa che potresti fare è cambiare Sort modo che faccia un uso migliore di lambda.

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

Ora puoi specificare il campo da ordinare quando chiami il metodo Sort .

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

È ansible utilizzare Reflection per ottenere il valore della proprietà.

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

Dove TypeHelper ha un metodo statico come:

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

Si potrebbe anche voler vedere Dynamic LINQ dalla libreria VS2008 Samples . È ansible utilizzare l’estensione IEnumerable per eseguire il cast dell’elenco come IQueryable e quindi utilizzare l’estensione OrderBy del collegamento dinamico.

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

Ecco come ho risolto il mio 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(); } 

Costruire l’ordine per espressione può essere letto qui

Rubato senza vergogna dalla pagina in 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(); 

È ansible utilizzare la riflessione per accedere alla proprietà.

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

Gli appunti

  1. Perché passi la lista per riferimento?
  2. Dovresti usare un enum per la direzione del tipo.
  3. È ansible ottenere una soluzione molto più pulita se si passerebbe un’espressione lambda che specifica la proprietà da ordinare anziché il nome della proprietà come stringa.
  4. Nella mia lista di esempio == null causerà una NullReferenceException, dovresti prendere questo caso.

Ordina usa l’interfaccia IComparable, se il tipo lo implementa. Ed è ansible evitare i if implementando un IComparer personalizzato:

 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 poi

 list.Sort(new EmpComp(sortBy)); 

Risposta per 1 .:

Dovresti essere in grado di build manualmente un albero di espressioni che può essere passato in OrderBy usando il nome come stringa. Oppure puoi usare la riflessione come suggerito in un’altra risposta, che potrebbe essere meno utile.

Modifica : qui c’è un esempio funzionante di costruzione manuale di un albero di espressioni. (Ordinamento su X.Value, quando si conosce solo il nome “Valore” della proprietà). Potresti (dovresti) creare un metodo generico per farlo.

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

Costruire un albero di espressione richiede tuttavia di conoscere i tipi di particpating. Questo potrebbe o potrebbe non essere un problema nel tuo scenario di utilizzo. Se non sai quale tipo dovresti selezionare, sarà probabilmente più facile usare la riflessione.

Risposta per 2 .:

Sì, poiché Comparator .Default verrà utilizzato per il confronto, se non si definisce esplicitamente il comparatore.

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

Un altro, questa volta per qualsiasi 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))); } } 

Puoi passare più criteri di ordinamento, come questo:

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

La soluzione fornita da Rashack non funziona per i tipi di valore (int, enumerati, ecc.) Purtroppo.

Perché funzioni con qualsiasi tipo di proprietà, questa è la soluzione che ho trovato:

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