Comment trier une liste <T> par une propriété dans l'objet


1250

J'ai une classe appelée Orderqui a des propriétés telles que OrderId, OrderDate, Quantity, et Total. J'ai une liste de cette Orderclasse:

List<Order> objListOrder = new List<Order>();
GetOrderList(objListOrder); // fill list of orders

Maintenant, je veux trier la liste en fonction d'une propriété de l' Orderobjet, par exemple, je dois la trier par la date ou l'id de la commande.

Comment puis-je faire cela en C #?


9
Attendez - vous voulez trier à la fois la date et l' id de la commande? Parce que la plupart des réponses semblent supposer que vous ne triez que par l'un ou l'autre.
GalacticCowboy

@GalacticCowboy, @GenericTypeTea: La question dit "je veux trier la liste en fonction d' une propriété de l'objet Order", mais je reconnais qu'ailleurs ce n'est pas complètement clair. Quoi qu'il en soit, cela ne fait pas une énorme différence si vous devez trier par une ou plusieurs propriétés.
LukeH

@LukeH - Je viens juste d'avoir une quatrième relecture et je suis d'accord. 'je veux trier la liste en fonction d'une propriété' ... et 'date de commande ou identifiant de commande'. Entre toutes les réponses, nous avons toutes les bases couvertes :).
djdd87

Réponses:


1808

La façon la plus simple à laquelle je peux penser est d'utiliser Linq:

List<Order> SortedList = objListOrder.OrderBy(o=>o.OrderDate).ToList();

22
comment puis-je trier cela dans l'ordre décroissant.
Cute Bear

229
@BonusKun List <Order> SortedList = objListOrder. OrderByDescending (o => o.OrderDate) .ToList ();
javajavajavajavajava

77
notez que cela crée une toute nouvelle liste avec tous les éléments en mémoire, ce qui peut être problématique en termes de performances.
staafl

14
@staafl listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();suffira pour une telle entreprise?
Andrew Grinder

28
@staafl Nous commandons une liste de références d'objets, sans dupliquer les objets eux-mêmes pour autant que je sache. Bien que cela double la mémoire utilisée par la liste de références, ce n'est pas aussi mauvais que de dupliquer tous les objets eux-mêmes, donc dans la plupart des scénarios en dehors de ceux où nous traitons d'énormes ensembles de données et les conserver en mémoire est déjà un problème, alors cela devrait suffire.
Lazarus

798

Si vous devez trier la liste sur place, vous pouvez utiliser la Sortméthode en passant un Comparison<T>délégué:

objListOrder.Sort((x, y) => x.OrderDate.CompareTo(y.OrderDate));

Si vous préférez créer une nouvelle séquence triée plutôt que de trier sur place, vous pouvez utiliser la OrderByméthode de LINQ , comme mentionné dans les autres réponses.


30
Oui, c'est la réponse "correcte" et devrait être beaucoup plus efficace que de créer un nouvel IEnumerable et de le convertir ensuite en une nouvelle liste.
Jonathan Wood

45
Bien sûr, si vous avez besoin d'un tri décroissant, permutez xet ysur le côté droit de la flèche =>.
Jeppe Stig Nielsen

15
La vraie réponse qui trie la liste sur place
Mitchell Currie

3
@PimBrouwers C'est la meilleure période d'option. Même si la quantité de mémoire que vous utilisez n'est pas un problème, cette solution évite une allocation de mémoire inutile qui est EXTRÊMEMENT chère. Cette option est tout aussi simple au niveau du code et un ordre de grandeur plus rapide.
Cdaragorn

12
@JonSchneider Pour Nullable<>(prenez DateTime?l'exemple), vous pouvez utiliser .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))ce qui traitera null comme précédant toutes les valeurs non nulles. C'est exactement la même chose que .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Jeppe Stig Nielsen

219

Pour ce faire sans LINQ sur .Net2.0:

List<Order> objListOrder = GetOrderList();
objListOrder.Sort(
    delegate(Order p1, Order p2)
    {
        return p1.OrderDate.CompareTo(p2.OrderDate);
    }
);

Si vous êtes sur .Net3.0, alors la réponse de LukeH est ce que vous recherchez.

Pour trier sur plusieurs propriétés, vous pouvez toujours le faire au sein d'un délégué. Par exemple:

orderList.Sort(
    delegate(Order p1, Order p2)
    {
        int compareDate = p1.Date.CompareTo(p2.Date);
        if (compareDate == 0)
        {
            return p2.OrderID.CompareTo(p1.OrderID);
        }
        return compareDate;
    }
);

Cela vous donnerait des dates ascendantes avec des ID de commande décroissants .

Cependant, je ne recommanderais pas de coller les délégués car cela signifiera beaucoup d'endroits sans réutilisation de code. Vous devez implémenter un ICompareret simplement le transmettre à votre Sortméthode. Voyez ici .

public class MyOrderingClass : IComparer<Order>
{
    public int Compare(Order x, Order y)
    {
        int compareDate = x.Date.CompareTo(y.Date);
        if (compareDate == 0)
        {
            return x.OrderID.CompareTo(y.OrderID);
        }
        return compareDate;
    }
}

Et puis, pour utiliser cette classe IComparer, instanciez-la simplement et passez-la à votre méthode Sort:

IComparer<Order> comparer = new MyOrderingClass();
orderList.Sort(comparer);

1
Excellente réponse, et devrait être la bonne car elle protège la réinitialisation de la liste d'origine (ce que la version LINQ fera toujours) offrant une meilleure encapsulation.
Jeb

@wonton, non. L'idée est de pouvoir avoir différentes IComparerimplémentations, nous donnant un comportement polymorphe.
radarbob

A travaillé pour moi. J'en ai publié une version avec des options de tri.
Jack Griffin

109

La manière la plus simple de commander une liste est d'utiliser OrderBy

 List<Order> objListOrder = 
    source.OrderBy(order => order.OrderDate).ToList();

Si vous souhaitez commander par plusieurs colonnes, comme la requête SQL suivante.

ORDER BY OrderDate, OrderId

Pour y parvenir, vous pouvez utiliser ThenBycomme suit.

  List<Order> objListOrder = 
    source.OrderBy(order => order.OrderDate).ThenBy(order => order.OrderId).ToList();

32

Le faire sans Linq comme vous l'avez dit:

public class Order : IComparable
{
    public DateTime OrderDate { get; set; }
    public int OrderId { get; set; }

    public int CompareTo(object obj)
    {
        Order orderToCompare = obj as Order;
        if (orderToCompare.OrderDate < OrderDate || orderToCompare.OrderId < OrderId)
        {
            return 1;
        }
        if (orderToCompare.OrderDate > OrderDate || orderToCompare.OrderId > OrderId)
        {
            return -1;
        }

        // The orders are equivalent.
        return 0;
    }
}

Ensuite, appelez simplement .sort () sur votre liste de commandes


3
Il faut d'abord tester nullsur le ascasting. C'est tout l'intérêt de as, car (ha, ha) (Order)objlève une exception en cas d'échec. if(orderToCompare == null) return 1;.
radarbob

+1 pour l'utilisation de l'interface, ce qui facilite la maintenance du code et expose clairement les capacités de l'objet.
AeonOfTime

Si Bill et Ted se présentent, je leur demanderai de me ramener au 16 octobre 2014 afin que je puisse corriger mon erreur ci-dessus - asretourne nullsi le casting échoue. Mais au moins, le test nul a raison.
radarbob

@radarbob ouais .. oups. :) Mais! Cette fonction est destinée à être utilisée automatiquement par un tri sur une liste qui est List<Order>donc le type doit être garanti pour correspondre sur le as2014 donc vous n'avez probablement pas écrit un bug, autant que d'éviter une déclaration de garde inutile :)
Jimmy Hoffa

devrait être garanti Point intéressant. S'il est bien encapsulé de telle sorte qu'il ne peut être appelé que pour passer a List<Order>; mais vous et moi avons tous deux rencontré le programmeur auto-encapsulé dont la présomption tacite est "j'écris ce code pour qu'il ne soit pas mal utilisé"
radarbob

26

Une solution orientée objet classique

Je dois d'abord faire une génuflexion sur le caractère génial de LINQ .... Maintenant que nous avons mis cela de côté

Une variation sur la réponse de JimmyHoffa. Avec les génériques, le CompareToparamètre devient de type sécurisé.

public class Order : IComparable<Order> {

    public int CompareTo( Order that ) {
        if ( that == null ) return 1;
        if ( this.OrderDate > that.OrderDate) return 1;
        if ( this.OrderDate < that.OrderDate) return -1;
        return 0;
    }
}

// in the client code
// assume myOrders is a populated List<Order>
myOrders.Sort(); 

Cette triabilité par défaut est bien sûr réutilisable. Autrement dit, chaque client n'a pas à réécrire de manière redondante la logique de tri. L'échange du "1" et du "-1" (ou des opérateurs logiques, votre choix) inverse l'ordre de tri.


Approche simple pour trier les objets dans une liste. Mais je ne comprends pas pourquoi vous retournez 1 if (that == null)?
Loc Huynh

4
cela signifie que l' thisobjet est supérieur à null. Aux fins du tri, une référence d'objet nul "est inférieure à" thisobjet. C'est juste la façon dont j'ai décidé de définir comment les valeurs nulles seront triées.
radarbob

18

// Tri totalement générique à utiliser avec une vue en grille

public List<T> Sort_List<T>(string sortDirection, string sortExpression, List<T> data)
    {

        List<T> data_sorted = new List<T>();

        if (sortDirection == "Ascending")
        {
            data_sorted = (from n in data
                              orderby GetDynamicSortProperty(n, sortExpression) ascending
                              select n).ToList();
        }
        else if (sortDirection == "Descending")
        {
            data_sorted = (from n in data
                              orderby GetDynamicSortProperty(n, sortExpression) descending
                              select n).ToList();

        }

        return data_sorted;

    }

    public object GetDynamicSortProperty(object item, string propName)
    {
        //Use reflection to get order type
        return item.GetType().GetProperty(propName).GetValue(item, null);
    }

4
Ou bien , vous savez, data.OrderBy(). Un peu plus facile que de réinventer la roue.
gunr2171

4
L'idée est que cela fonctionnera avec n'importe quel type d'objet. Where as OrderBy () ne fonctionne qu'avec des objets fortement typés.
roger

1
Thx beaucoup.À mon avis, la solution de tri de données la plus simple et la plus utilisable!
kostas ch.

6

Voici une méthode d'extension LINQ générique qui ne crée pas de copie supplémentaire de la liste:

public static void Sort<T,U>(this List<T> list, Func<T, U> expression)
    where U : IComparable<U>
{
    list.Sort((x, y) => expression.Invoke(x).CompareTo(expression.Invoke(y)));
}

Pour l'utiliser:

myList.Sort(x=> x.myProperty);

J'ai récemment construit celui-ci supplémentaire qui accepte un ICompare<U>, afin que vous puissiez personnaliser la comparaison. Cela m'a été utile lorsque j'ai dû faire un tri de chaîne naturelle:

public static void Sort<T, U>(this List<T> list, Func<T, U> expression, IComparer<U> comparer)
    where U : IComparable<U>
{    
    list.Sort((x, y) => comparer.Compare(expression.Invoke(x), expression.Invoke(y)));
}

J'ai mis en œuvre cela, fonctionne très bien. J'ai ajouté un paramètre "isAscending = true". Pour trier par ordre décroissant, permutez simplement les x et y autour dans les deux méthodes Invoke (). Merci.
Rob L

Si vous allez simplement compiler l'expression, vous pouvez tout aussi bien accepter un délégué pour commencer. De plus, cette approche présente des problèmes majeurs si le sélecteur provoque des effets secondaires, est coûteux à calculer ou n'est pas déterministe. Un tri correct basé sur une expression sélectionnée doit appeler le sélecteur pas plus d'une fois par élément de la liste.
Servy

@Servy - ce sont tous des points valables, mais je vais être honnête, je ne sais pas comment mettre en œuvre certaines de vos suggestions (en particulier empêcher un sélecteur de provoquer des effets secondaires ...?). Je les ai changés en délégués. Si vous savez comment apporter certaines de ces modifications, je serais heureux de vous faire éditer mon code.
Peter

3
@Peter Vous ne pouvez pas empêcher les délégués de provoquer des effets secondaires. Ce que vous pouvez faire est de veiller à ce qu'ils ne sont jamais appelés plus d'une fois par objet, ce moyen de calcul des valeurs pour chaque objet, stocker les paires objet / valeur projetée, puis le tri que . OrderByfait tout cela en interne.
Servy

Une autre belle façon d'écrire une voidméthode d'extension dans cet esprit: static class Extensions { public static void SortBy<TSource, TKey>(this List<TSource> self, Func<TSource, TKey> keySelector) { self.SortBy(keySelector, Comparer<TKey>.Default); } public static void SortBy<TSource, TKey>(this List<TSource> self, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) { self.Sort((x, y) => comparer.Compare(keySelector(x), keySelector(y))); } }peut être complétée par une SortByDescendingméthode. Les commentaires de @ Servy s'appliquent également à mon code!
Jeppe Stig Nielsen

4

Utilisation de LINQ

objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderDate)
                   .ToList();

objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderId)
                   .ToList();

3
//Get data from database, then sort list by staff name:

List<StaffMember> staffList = staffHandler.GetStaffMembers();

var sortedList = from staffmember in staffList
                 orderby staffmember.Name ascending
                 select staffmember;

3

Une amélioration de la version de Roger.

Le problème avec GetDynamicSortProperty est que seulement obtenir les noms de propriété, mais que se passe-t-il si dans le GridView nous utilisons NavigationProperties? il enverra une exception, car il trouve null.

Exemple:

"Employee.Company.Name;" se bloque ... car n'autorise que "Name" comme paramètre pour obtenir sa valeur.

Voici une version améliorée qui nous permet de trier par les propriétés de navigation.

public object GetDynamicSortProperty(object item, string propName)
    {
        try
        {                 
            string[] prop = propName.Split('.'); 

            //Use reflection to get order type                   
            int i = 0;                    
            while (i < prop.Count())
            {
                item = item.GetType().GetProperty(prop[i]).GetValue(item, null);
                i++;
            }                     

            return item;
        }
        catch (Exception ex)
        {
            throw ex;
        }


    } 

3

Vous pouvez faire quelque chose de plus générique sur la sélection des propriétés tout en étant précis sur le type à partir duquel vous sélectionnez, dans votre cas «Ordre»:

écrivez votre fonction comme une fonction générique:

public List<Order> GetOrderList<T>(IEnumerable<Order> orders, Func<Order, T> propertySelector)
        {
            return (from order in orders
                    orderby propertySelector(order)
                    select order).ToList();
        } 

puis utilisez-le comme ceci:

var ordersOrderedByDate = GetOrderList(orders, x => x.OrderDate);

Vous pouvez être encore plus générique et définir un type ouvert pour ce que vous souhaitez commander:

public List<T> OrderBy<T,P>(IEnumerable<T> collection, Func<T,P> propertySelector)
        {
            return (from item in collection
                    orderby propertySelector(item)
                    select item).ToList();
        } 

et utilisez-le de la même manière:

var ordersOrderedByDate = OrderBy(orders, x => x.OrderDate);

Ce qui est une manière complexe et stupide et inutile de faire un `` OrderBy '' de style LINQ, mais cela peut vous donner un indice sur la façon dont il peut être implémenté de manière générique


3

Veuillez me laisser compléter la réponse par @LukeH avec un exemple de code, car je l'ai testé, je pense que cela peut être utile pour certains:

public class Order
{
    public string OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public int Quantity { get; set; }
    public int Total { get; set; }

    public Order(string orderId, DateTime orderDate, int quantity, int total)
    {
        OrderId = orderId;
        OrderDate = orderDate;
        Quantity = quantity;
        Total = total;
    }
}

public void SampleDataAndTest()
{
    List<Order> objListOrder = new List<Order>();

    objListOrder.Add(new Order("tu me paulo ", Convert.ToDateTime("01/06/2016"), 1, 44));
    objListOrder.Add(new Order("ante laudabas", Convert.ToDateTime("02/05/2016"), 2, 55));
    objListOrder.Add(new Order("ad ordinem ", Convert.ToDateTime("03/04/2016"), 5, 66));
    objListOrder.Add(new Order("collocationem ", Convert.ToDateTime("04/03/2016"), 9, 77));
    objListOrder.Add(new Order("que rerum ac ", Convert.ToDateTime("05/02/2016"), 10, 65));
    objListOrder.Add(new Order("locorum ; cuius", Convert.ToDateTime("06/01/2016"), 1, 343));


    Console.WriteLine("Sort the list by date ascending:");
    objListOrder.Sort((x, y) => x.OrderDate.CompareTo(y.OrderDate));

    foreach (Order o in objListOrder)
        Console.WriteLine("OrderId = " + o.OrderId + " OrderDate = " + o.OrderDate.ToString() + " Quantity = " + o.Quantity + " Total = " + o.Total);

    Console.WriteLine("Sort the list by date descending:");
    objListOrder.Sort((x, y) => y.OrderDate.CompareTo(x.OrderDate));
    foreach (Order o in objListOrder)
        Console.WriteLine("OrderId = " + o.OrderId + " OrderDate = " + o.OrderDate.ToString() + " Quantity = " + o.Quantity + " Total = " + o.Total);

    Console.WriteLine("Sort the list by OrderId ascending:");
    objListOrder.Sort((x, y) => x.OrderId.CompareTo(y.OrderId));
    foreach (Order o in objListOrder)
        Console.WriteLine("OrderId = " + o.OrderId + " OrderDate = " + o.OrderDate.ToString() + " Quantity = " + o.Quantity + " Total = " + o.Total);

    //etc ...
}

3
var obj = db.Items.Where...

var orderBYItemId = obj.OrderByDescending(c => Convert.ToInt32(c.ID));

1

Utilisez LiNQ OrderBy

List<Order> objListOrder=new List<Order> ();
    objListOrder=GetOrderList().OrderBy(o=>o.orderid).ToList();

1

Basé sur Comparer de GenericTypeTea:
nous pouvons obtenir plus de flexibilité en ajoutant des drapeaux de tri:

public class MyOrderingClass : IComparer<Order> {  
    public int Compare(Order x, Order y) {  
        int compareDate = x.Date.CompareTo(y.Date);  
        if (compareDate == 0) {  
            int compareOrderId = x.OrderID.CompareTo(y.OrderID);  

            if (OrderIdDescending) {  
                compareOrderId = -compareOrderId;  
            }  
            return compareOrderId;  
        }  

        if (DateDescending) {  
            compareDate = -compareDate;  
        }  
        return compareDate;  
    }  

    public bool DateDescending { get; set; }  
    public bool OrderIdDescending { get; set; }  
}  

Dans ce scénario, vous devez l'instancier explicitement en tant que MyOrderingClass (plutôt qu'IComparer )
afin de définir ses propriétés de tri:

MyOrderingClass comparer = new MyOrderingClass();  
comparer.DateDescending = ...;  
comparer.OrderIdDescending = ...;  
orderList.Sort(comparer);  

1

Aucune des réponses ci-dessus n'était assez générique pour moi, alors j'ai fait celle-ci:

var someUserInputStringValue = "propertyNameOfObject i.e. 'Quantity' or 'Date'";
var SortedData = DataToBeSorted
                   .OrderBy(m => m.GetType()
                                  .GetProperties()
                                  .First(n => 
                                      n.Name == someUserInputStringValue)
                   .GetValue(m, null))
                 .ToList();

Attention toutefois aux ensembles de données massifs. C'est un code facile mais pourrait vous causer des ennuis si la collection est énorme et que le type d'objet de la collection a un grand nombre de champs. Le temps d'exécution est NxM où:

N = nombre d'éléments dans la collection

M = nombre de propriétés dans l'objet


1

Toute personne travaillant avec des types nullables Valuedoit utiliser CompareTo.

objListOrder.Sort((x, y) => x.YourNullableType.Value.CompareTo(y.YourNullableType.Value));


0

Du point de vue des performances, le mieux est d'utiliser une liste triée afin que les données soient triées au fur et à mesure qu'elles sont ajoutées au résultat. D'autres approches nécessitent au moins une itération supplémentaire sur les données et la plupart créent une copie des données afin que non seulement les performances, mais l'utilisation de la mémoire soient également affectées. Peut ne pas être un problème avec quelques centaines d'éléments mais le sera avec des milliers, en particulier dans les services où de nombreuses demandes simultanées peuvent effectuer un tri en même temps. Jetez un œil à l'espace de noms System.Collections.Generic et choisissez une classe avec tri au lieu de List.

Et évitez les implémentations génériques utilisant la réflexion lorsque cela est possible, cela peut également entraîner des problèmes de performances.


Comment cela peut-il être plus performant? L'insertion de données dans une liste triée est O (n), donc l'ajout de n éléments est O (n ^ 2). Qu'est-ce que de nombreux éléments simultanés tentent d'ajouter? Il y a des situations où vous avez des cycles CPU supplémentaires à graver pendant l'ajout d'éléments, mais ce n'est pas une bonne solution générale.
John La Rooy

0

Supposons que vous ayez le code suivant, dans ce code, nous avons une classe Passenger avec quelques propriétés sur lesquelles nous voulons trier.

public class Passenger
{
    public string Name { get; }
    public string LastName { get; }
    public string PassportNo { get; }
    public string Nationality { get; }

    public Passenger(string name, string lastName, string passportNo, string nationality)
    {
        this.Name = name;
        this.LastName = lastName;
        this.PassportNo = passportNo;
        this.Nationality = nationality;
    }

    public static int CompareByName(Passenger passenger1, Passenger passenger2)
    {
        return String.Compare(passenger1.Name, passenger2.Name);
    }

    public static int CompareByLastName(Passenger passenger1, Passenger passenger2)
    {
        return String.Compare(passenger1.LastName, passenger2.LastName);
    }

    public static int CompareNationality(Passenger passenger1, Passenger passenger2)
    {
        return String.Compare(passenger1.Nationality, passenger2.Nationality);
    }
}

public class TestPassengerSort
{
    Passenger p1 = new Passenger("Johon", "Floid", "A123456789", "USA");
    Passenger p2 = new Passenger("Jo", "Sina", "A987463215", "UAE");
    Passenger p3 = new Passenger("Ped", "Zoola", "A987855215", "Italy");

    public void SortThem()
    {
        Passenger[] passengers = new Passenger[] { p1, p2, p3 };
        List<Passenger> passengerList = new List<Passenger> { p1, p2, p3 };

        Array.Sort(passengers, Passenger.CompareByName);
        Array.Sort(passengers, Passenger.CompareByLastName);
        Array.Sort(passengers, Passenger.CompareNationality);

        passengerList.Sort(Passenger.CompareByName);
        passengerList.Sort(Passenger.CompareByLastName);
        passengerList.Sort(Passenger.CompareNationality);

    }
}

Vous pouvez donc implémenter votre structure de tri en utilisant Délégué de composition.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.