web-dev-qa-db-fra.com

Tri d'une liste en utilisant Lambda / Linq en objets

J'ai le nom du "sort by property" dans une chaîne. J'aurai besoin d'utiliser Lambda/Linq pour trier la liste d'objets.

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

}
  1. Au lieu d'utiliser une série de ifs pour vérifier le nom de champ (sortBy), existe-t-il une méthode plus propre pour effectuer le tri
  2. Le tri est-il conscient du type de données?
261
DotnetDude

Cela peut être fait comme

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

Le framework .NET transforme le lambda (emp1,emp2)=>int en tant que Comparer<Employee>.

Cela a l'avantage d'être fortement typé.

345
gls123

Une chose que vous pouvez faire est de changer Sort afin de mieux utiliser les 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);
}

Vous pouvez maintenant spécifier le champ à trier lorsque vous appelez la méthode Sort.

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

Vous pouvez utiliser Reflection pour obtenir la valeur de la propriété.

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

Où TypeHelper a une méthode statique comme:

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

Vous pouvez également consulter Dynamic LINQ à partir de bibliothèque des exemples VS2008 . Vous pouvez utiliser l'extension IEnumerable pour convertir la liste en un IQueryable, puis utiliser l'extension Dynamic Link OrderBy.

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

Voici comment j'ai résolu mon problème:

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

Construire la commande par expression peut être lu ici

Volé sans vergogne de la page en lien:

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

Vous pouvez utiliser la réflexion pour accéder à la propriété.

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

Notes

  1. Pourquoi passez-vous la liste par référence?
  2. Vous devriez utiliser une énumération pour la direction du tri.
  3. Vous pourriez obtenir une solution beaucoup plus propre si vous passiez une expression lambda spécifiant la propriété à trier au lieu du nom de la propriété sous forme de chaîne.
  4. Dans mon exemple, list == null provoquera une exception NullReferenceException, vous devriez intercepter ce cas.
8
Daniel Brückner

Sort utilise l'interface IComparable, si le type l'implémente. Et vous pouvez éviter les ifs en implémentant un IComparer personnalisé:

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

puis

list.Sort(new EmpComp(sortBy));
6
Serguei

Réponse pour 1.:

Vous devriez pouvoir créer manuellement un arbre d’expression pouvant être passé à OrderBy en utilisant le nom sous forme de chaîne. ou vous pouvez utiliser la réflexion comme suggéré dans une autre réponse, ce qui pourrait être moins laborieux.

Edit : Voici un exemple concret de création manuelle d’un arbre d’expression. (Tri sur X.Value, lorsque vous ne connaissez que le nom "Valeur" de la propriété). Vous pourriez (devriez) construire une méthode générique pour le faire.

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

Construire un arbre d’expression nécessite cependant de connaître les types de particpating. Cela pourrait ou non être un problème dans votre scénario d'utilisation. Si vous ne savez pas sur quel type vous devriez trier, il sera probablement plus facile d'utiliser la réflexion.

Réponse pour 2.:

Oui, puisque Comparer <T> .Default sera utilisé pour la comparaison, si vous ne définissez pas explicitement le comparateur.

5
driis

La solution fournie par Rashack ne fonctionne malheureusement pas pour les types de valeur (int, enums, etc.).

Pour que cela fonctionne avec tout type de propriété, voici la solution que j'ai trouvée:

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

}

Un autre, cette fois pour tout 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)));
    }

}

Vous pouvez passer plusieurs critères de tri, comme ceci:

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