Come ordinare un elenco <T> per una proprietà nell'oggetto


1249

Ho una classe chiamata Orderche ha proprietà quali OrderId, OrderDate, Quantity, e Total. Ho un elenco di questoOrder classe:

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

Ora voglio ordinare l'elenco in base a una proprietà di Order dell'oggetto, ad esempio ho bisogno di ordinarlo per data dell'ordine o ID ordine.

Come posso farlo in C #?


9
Aspetta: vuoi ordinare per data e ID ordine? Perché la maggior parte delle risposte sembra presumere che stai ordinando solo l'uno o l'altro.
GalacticCowboy,

@GalacticCowboy, @GenericTypeTea: la domanda dice "Voglio ordinare l'elenco in base a una proprietà dell'oggetto Ordine", ma sono d'accordo che altrove non è completamente chiaro. Ad ogni modo, non fa un'enorme differenza se devi ordinare per una o più proprietà.
Luca,

@LukeH - Ho appena avuto una quarta rilettura e sono d'accordo. "Voglio ordinare l'elenco in base a una proprietà" ... e "Data ordine o ID ordine". Tra tutte le risposte, abbiamo coperto tutte le basi :).
djdd87,

Risposte:


1807

Il modo più semplice a cui riesco a pensare è usare Linq:

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

22
come posso ordinare questo in ordine decrescente.
Simpatico orso

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

77
si noti che questo crea un elenco completamente nuovo con tutti gli elementi in memoria, che può essere problematico in termini di prestazioni.
staafl

14
@staafl sarà listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();sufficiente per un tale sforzo?
Andrew Grinder,

28
@staafl Stiamo ordinando un elenco di riferimenti a oggetti, non duplicando gli oggetti stessi per quanto ne so. Anche se questo raddoppia la memoria utilizzata dall'elenco dei riferimenti, non è così grave come duplicare effettivamente tutti gli oggetti stessi, quindi nella maggior parte degli scenari al di fuori di quelli in cui abbiamo a che fare con enormi set di dati e tenerli in memoria è già un problema, quindi dovrebbe bastare.
Lazzaro,

798

Se è necessario ordinare l'elenco sul posto, è possibile utilizzare il Sortmetodo, passando un Comparison<T>delegato:

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

Se si preferisce creare una nuova sequenza ordinata anziché ordinarla sul posto, è possibile utilizzare il OrderBymetodo LINQ , come indicato nelle altre risposte.


30
Sì, questa è la risposta "corretta" e dovrebbe essere molto più efficiente della creazione di un nuovo IEnumerable e quindi della sua conversione in un nuovo elenco.
Jonathan Wood,

45
Naturalmente, se hai bisogno di un ordinamento decrescente, scambia xe ysul lato destro della freccia =>.
Jeppe Stig Nielsen,

15
La vera risposta che ordina effettivamente l'elenco sul posto
Mitchell Currie il

3
@PimBrouwers Questo è il periodo di opzione migliore. Anche se la quantità di memoria che stai utilizzando non è un problema, questa soluzione evita l'allocazione di memoria non necessaria che è ESTREMAMENTE costosa. Questa opzione è altrettanto semplice per codice e un ordine di grandezza più veloce.
Cdaragorn,

12
@JonSchneider For Nullable<>(prendi DateTime?come esempio) puoi usare .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))che tratterà null come prima di tutti i valori non null. È esattamente lo stesso di .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Jeppe Stig Nielsen,

219

Per fare ciò senza LINQ su .Net2.0:

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

Se sei su .Net3.0, allora la risposta di LukeH è ciò che stai .

Per ordinare su più proprietà, è ancora possibile farlo all'interno di un delegato. Per esempio:

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

Questo ti darebbe date ascendenti con ordini decrescenti .

Tuttavia, non consiglierei di attaccare i delegati poiché significherà molti posti senza riutilizzo del codice. Dovresti implementare un IComparere passarlo al tuo Sortmetodo. Vedi qui .

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

E quindi per usare questa classe IComparer, basta istanziarla e passarla al metodo Sort:

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

1
Risposta eccellente, e dovrebbe essere quella corretta in quanto protegge la reinizializzazione dell'elenco originale (che la versione LINQ farà sempre) fornendo un migliore incapsulamento.
Jeb,

@wonton, no. L'idea è quella di poter avere diverse IComparerimplementazioni, dandoci un comportamento polimorfico.
radarbob,

Ha funzionato per me. Ho pubblicato una versione di questo con opzioni di ordinamento.
Jack Griffin,

109

Utilizzare il modo più semplice per ordinare un elenco OrderBy

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

Se vuoi ordinare per più colonne come seguendo la query SQL.

ORDER BY OrderDate, OrderId

Per raggiungere questo obiettivo è possibile utilizzare ThenBycome segue.

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

32

Farlo senza Linq come hai detto:

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

Quindi chiama .sort () nel tuo elenco di ordini


3
Bisogna prima provare nullsul ascast. Qual è il punto centrale di as, poiché (ah, ah) (Order)objgenera un'eccezione quando fallisce. if(orderToCompare == null) return 1;.
radarbob,

+1 per l'utilizzo dell'interfaccia, che semplifica la manutenzione del codice ed espone chiaramente le capacità dell'oggetto.
AeonOfTime

Se Bill e Ted dovessero presentarsi, chiederò loro di riportarmi al 16 ottobre 2014 in modo che io possa correggere il mio errore sopra - asritorna nullse il cast fallisce. Ma almeno il null-test ha ragione.
radarbob,

@radarbob yeah .. oops. :) Ma! Questa funzione dovrebbe essere utilizzata automaticamente da un ordinamento su un elenco che è List<Order>quindi il tipo dovrebbe essere garantito per corrispondere nel as2014 quindi probabilmente non hai scritto un bug, tanto da evitare una dichiarazione di guardia non necessaria :)
Jimmy Hoffa

dovrebbe essere garantito Punto interessante. Se è ben incapsulato in modo tale che non può essere chiamato se non per passare a List<Order>; ma entrambi abbiamo incontrato il programmatore autoincapsulato la cui presunzione non espressa è "sto scrivendo questo codice in modo che non venga usato in modo errato"
radarbob

26

Una soluzione classica orientata agli oggetti

Per prima cosa devo genuflect alla meraviglia di LINQ .... Ora che ce l'abbiamo fatta

Una variazione sulla risposta di JimmyHoffa. Con generics il CompareToparametro diventa type safe.

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

Questa ordinabilità predefinita è riutilizzabile, ovviamente. Ciò significa che ogni client non deve riscrivere in modo ridondante la logica di ordinamento. Scambiando "1" e "-1" (o gli operatori logici, a scelta) si inverte l'ordinamento.


Approccio semplice per l'ordinamento di oggetti in un elenco. Ma non capisco perché si restituisce 1 if (that == null)?
Loc Huynh,

4
significa che l' thisoggetto è maggiore di null. Ai fini dell'ordinamento, un riferimento a oggetto null "è minore di" thisoggetto. Questo è solo il modo in cui ho deciso di definire come ordinare i null.
radarbob,

18

// Ordinamento totalmente generico da utilizzare con una griglia

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
Oppure, si sa, data.OrderBy(). Un po 'più facile che reinventare la ruota.
gunr2171,

4
L'idea è che funzionerà con qualsiasi tipo di oggetto. Where as OrderBy () funziona solo con oggetti fortemente tipizzati.
Roger

1
Grazie mille. A mio avviso, la soluzione più semplice e utilizzabile per l'ordinamento dei dati della griglia!
kostas ch.

6

Ecco un metodo di estensione LINQ generico che non crea una copia aggiuntiva dell'elenco:

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

Per usarlo:

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

Di recente ho creato questo ulteriore che accetta un ICompare<U>, in modo da poter personalizzare il confronto. Questo è stato utile quando avevo bisogno di fare un ordinamento di stringhe naturali:

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

L'ho implementato, funziona benissimo. Ho aggiunto un parametro "isAscending = true". Per ordinare in ordine decrescente, scambia semplicemente xey all'interno dei due metodi Invoke (). Grazie.
Rob L

Se hai intenzione di compilare l'espressione potresti anche accettare un delegato per cominciare. Inoltre, questo approccio ha grossi problemi se il selettore provoca effetti collaterali, è costoso da calcolare o non è deterministico. Un ordinamento corretto basato su un'espressione selezionata deve invocare il selettore non più di una volta per elemento nell'elenco.
Servito l'

@Servy - quelli sono tutti punti validi, ma sarò onesto, non sono sicuro di come implementare alcuni dei tuoi suggerimenti (in particolare impedendo a un selettore di causare effetti collaterali ...?). Li ho cambiati in delegati. Se sai come apportare alcune di queste modifiche, sarei felice di averti modificato il mio codice.
Peter,

3
@Peter Non puoi impedire ai delegati di causare effetti collaterali. Che cosa si può fare è garantire che siano mai chiamato più di una volta per ogni oggetto, questo mezzo di calcolo i valori per ciascun oggetto, immagazzinare le coppie oggetto / valore proiettate, e poi ordinamento che . OrderByfa tutto ciò internamente.
Servito l'

Un altro bel modo di scrivere un voidmetodo di estensione in questo spirito: 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))); } }può essere integrato da un SortByDescendingmetodo. I commenti di @ Servy si applicano anche al mio codice!
Jeppe Stig Nielsen,

4

Utilizzando 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

Un miglioramento della versione di Roger.

Il problema con GetDynamicSortProperty è che ottengono solo i nomi delle proprietà, ma cosa succede se in GridView utilizziamo NavigationProperties? invierà un'eccezione, poiché trova null.

Esempio:

"Employee.Company.Name;" andrà in crash ... poiché consente solo a "Nome" come parametro di ottenere il suo valore.

Ecco una versione migliorata che ci consente di ordinare per Proprietà di navigazione.

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

Puoi fare qualcosa di più generico sulla selezione delle proprietà ma essere specifico sul tipo da cui stai selezionando, nel tuo caso 'Ordine':

scrivi la tua funzione come generica:

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

e poi usalo in questo modo:

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

Puoi essere ancora più generico e definire un tipo aperto per ciò che desideri ordinare:

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

e usalo allo stesso modo:

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

Che è un modo stupido, complesso e superfluo per fare uno stile LINQ "OrderBy", ma può darti un'idea di come può essere implementato in modo generico


3

Per favore, lasciami completare la risposta di @LukeH con un po 'di codice di esempio, dato che l'ho provato credo che possa essere utile per alcuni:

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

Usa LiNQ OrderBy

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

1

Basato sul comparatore di GenericTypeTea :
possiamo ottenere una maggiore flessibilità aggiungendo flag di ordinamento:

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

In questo scenario, è necessario creare un'istanza come MyOrderingClass esplicitamente (anziché IComparer )
per impostare le proprietà di ordinamento:

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

1

Nessuna delle risposte sopra è stata abbastanza generica per me, quindi ho creato questo:

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

Attento su enormi set di dati però. È un codice semplice ma potrebbe metterti nei guai se la raccolta è enorme e il tipo di oggetto della raccolta ha un gran numero di campi. Il tempo di esecuzione è NxM dove:

N = # di elementi nella raccolta

M = # di proprietà all'interno dell'oggetto


1

Chiunque lavori con tipi nullable Valuedeve utilizzare CompareTo.

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


0

Dal punto di vista delle prestazioni, la cosa migliore è utilizzare un elenco ordinato in modo che i dati vengano ordinati man mano che vengono aggiunti ai risultati. Altri approcci richiedono almeno un'iterazione aggiuntiva sui dati e la maggior parte crea una copia dei dati, quindi non solo le prestazioni ma anche l'utilizzo della memoria ne risentiranno. Potrebbe non essere un problema con un paio di centinaia di elementi, ma lo sarà con migliaia, specialmente nei servizi in cui molte richieste simultanee possono eseguire l'ordinamento contemporaneamente. Dai un'occhiata allo spazio dei nomi System.Collections.Generic e scegli una classe con ordinamento anziché Elenco.

Ed evitare implementazioni generiche utilizzando la riflessione quando possibile, anche questo può causare problemi di prestazioni.


Come può essere più performante? L'inserimento di dati in un elenco ordinato è O (n), quindi l'aggiunta di n elementi è O (n ^ 2). Che cosa stanno cercando di aggiungere molti elementi simultanei? Ci sono situazioni in cui hai cicli di CPU aggiuntivi da masterizzare mentre vengono aggiunti elementi, ma questa non è una buona soluzione generale.
John La Rooy,

0

Supponiamo di avere il seguente codice, in questo codice, abbiamo una classe Passeggero con un paio di proprietà che vogliamo ordinare in base.

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

    }
}

Quindi puoi implementare la tua struttura di ordinamento usando il delegato Composizione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.