Jak posortować listę <T> według właściwości w obiekcie


1247

Mam klasy o nazwie Order, która ma właściwości takie jak OrderId, OrderDate, Quantity, i Total. Mam listę tej Orderklasy:

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

Teraz chcę posortować listę na podstawie jednej właściwości Orderobiektu, na przykład muszę posortować ją według daty zamówienia lub identyfikatora zamówienia.

Jak mogę to zrobić w C #?


9
Czekaj - chcesz sortować według daty zamówienia i identyfikatora zamówienia? Ponieważ większość odpowiedzi wydaje się zakładać, że sortujesz tylko według jednego lub drugiego.
GalacticCowboy

@GalacticCowboy, @GenericTypeTea: Pytanie brzmi: „chcę posortować listę na podstawie jednej właściwości obiektu Order”, ale zgadzam się, że gdzie indziej nie jest całkowicie jasne. Tak czy inaczej, nie ma wielkiego znaczenia, czy trzeba sortować według jednej, czy kilku właściwości.
Łukasza

@LukeH - Właśnie przeczytałem czwarty raz i zgadzam się. „chcę posortować listę według jednej właściwości” ... i „data zamówienia lub identyfikator zamówienia”. Pomiędzy wszystkimi odpowiedziami omówiliśmy wszystkie bazy :).
djdd87,

Odpowiedzi:


1806

Najprostszym sposobem, jaki mogę wymyślić, jest użycie Linq:

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

22
jak mogę to posortować w kolejności malejącej.
Słodki Niedźwiedź

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

77
zauważ, że tworzy to zupełnie nową listę ze wszystkimi elementami w pamięci, co może być problematyczne pod względem wydajności.
staafl

14
@staafl listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();wystarczy na takie przedsięwzięcie?
Andrew Grinder

28
@staafl Zamawiamy listę odwołań do obiektów, nie powielając samych obiektów, o ile mi wiadomo. Chociaż podwaja to pamięć używaną przez listę referencji, nie jest tak złe, jak powielanie wszystkich samych obiektów, więc w większości scenariuszy poza tymi, w których mamy do czynienia z ogromnymi zestawami danych i trzymanie ich w pamięci jest już problemem, to powinno wystarczyć.
Lazarus

798

Jeśli chcesz posortować listę w miejscu, możesz użyć Sortmetody, przekazując Comparison<T>delegata:

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

Jeśli wolisz utworzyć nową, posortowaną sekwencję niż sortować w miejscu, możesz użyć OrderBymetody LINQ , jak wspomniano w innych odpowiedziach.


30
Tak, jest to „poprawna” odpowiedź i powinna być znacznie wydajniejsza niż tworzenie nowej IEnumerable, a następnie konwertowanie jej na nową listę.
Jonathan Wood

45
Oczywiście, jeśli potrzebujesz sortowania malejącego, zamień xi ypo prawej stronie strzałki =>.
Jeppe Stig Nielsen,

15
Prawdziwa odpowiedź, która faktycznie sortuje listę w miejscu
Mitchell Currie,

3
@PimBrouwers To jest lepszy okres opcji. Nawet jeśli ilość używanej pamięci nie stanowi problemu, to rozwiązanie pozwala uniknąć niepotrzebnego przydziału pamięci, który jest BARDZO drogi. Ta opcja jest tak samo prosta i znacznie szybsza o rząd wielkości.
Cdaragorn,

12
@ JonSchneider Dla Nullable<>(weźmy DateTime?jako przykład) możesz użyć, .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))który będzie traktował wartość null jako poprzedzającą wszystkie wartości inne niż null. Jest dokładnie taki sam jak .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Jeppe Stig Nielsen

219

Aby to zrobić bez LINQ na .Net2.0:

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

Jeśli korzystasz z .Net3.0, to odpowiedź LukeH jest tym, czego szukasz.

Aby posortować według wielu właściwości, nadal możesz to zrobić w ramach delegata. Na przykład:

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

Dałoby to rosnące daty z malejącymi porządkami.

Nie zalecałbym jednak trzymania delegatów, ponieważ oznaczałoby to wiele miejsc bez ponownego użycia kodu. Powinieneś wdrożyć ICompareri po prostu przekazać to do swojej Sortmetody. Zobacz tutaj .

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

A następnie, aby użyć tej klasy IComparer, po prostu utwórz ją i przekaż do metody sortowania:

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

1
Doskonała odpowiedź i powinna być poprawna, ponieważ chroni ponowne inicjowanie oryginalnej listy (co zawsze zrobi wersja LINQ), zapewniając lepszą enkapsulację.
Jeb

@wonton, no. Chodzi o to, aby móc mieć różne IComparerimplementacje, co daje nam zachowanie polimorficzne.
radarbob

Pracował dla mnie. Opublikowałem wersję tego z opcjami sortowania.
Jack Griffin

109

Najprostszym sposobem na uporządkowanie listy jest użycie OrderBy

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

Jeśli chcesz zamówić według wielu kolumn, takich jak zapytanie SQL.

ORDER BY OrderDate, OrderId

Aby to osiągnąć, możesz wykonać ThenBynastępujące czynności.

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

32

Robisz to bez Linq, jak powiedziałeś:

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

Następnie po prostu wywołaj .sort () na liście zamówień


3
Najpierw trzeba przetestować nullna asobsadzie. To jest cały sens as, ponieważ (ha, ha) (Order)objzgłasza wyjątek, gdy zawodzi. if(orderToCompare == null) return 1;.
radarbob

+1 za korzystanie z interfejsu, który ułatwia utrzymanie kodu i wyraźnie ujawnia możliwości obiektu.
AeonOfTime,

Jeśli pojawią się Bill i Ted, poproszę ich, aby zabrali mnie z powrotem do 16 października 2014 roku, abym mógł poprawić swój błąd powyżej - aszwraca, nulljeśli rzutowanie się nie powiedzie. Ale przynajmniej test zerowy ma rację.
radarbob

@radarbob yeah .. ups. :) Ale! Ta funkcja jest przeznaczona do automatycznego używania przez sortowanie nad listą, List<Order>więc typ powinien być zgodny z as2014 roku, więc prawdopodobnie nie napisałeś błędu, tak samo jak unikanie niepotrzebnego polecenia straży :)
Jimmy Hoffa

należy zagwarantować Interesujący punkt. Jeśli jest dobrze zamknięty w taki sposób, że nie można go wywołać oprócz przekazania a List<Order>; ale oboje spotkaliśmy programistę, który nie
wypowiedział się,

26

Klasyczne rozwiązanie obiektowe

Najpierw muszę się przyzwyczaić do niesamowitości LINQ… Teraz, kiedy już to zrobiliśmy

Odmiana odpowiedzi JimmyHoffa. W przypadku generycznych CompareToparametr staje się bezpieczny dla typu.

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

Ta domyślna sortowalność jest oczywiście wielokrotnego użytku. Oznacza to, że każdy klient nie musi redundantnie zapisywać logiki sortowania. Zamiana „1” i „-1” (lub dowolnych operatorów logicznych) odwraca porządek sortowania.


Proste podejście do sortowania obiektów na liście. Ale nie rozumiem, dlaczego zwracasz 1 if (that == null)?
Loc Huynh

4
oznacza to, że thisobiekt jest większy niż zero. Do celów sortowania odwołanie do obiektu o wartości zerowej „jest mniejsze niż” thisobiekt. Właśnie w ten sposób zdecydowałem się zdefiniować sposób sortowania wartości null.
radarbob

18

// Całkowicie ogólne sortowanie do użytku z widokiem siatki

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
Lub wiesz data.OrderBy(). Nieco łatwiej niż na nowo wynaleźć koło.
gunr2171

4
Chodzi o to, że będzie działać z każdym typem obiektu. Gdzie as OrderBy () działa tylko z silnie wpisanymi obiektami.
roger

1
Bardzo dziękuję. Moim zdaniem najprostsze i użyteczne rozwiązanie do sortowania danych siatki!
kostas ch.

6

Oto ogólna metoda rozszerzenia LINQ, która nie tworzy dodatkowej kopii listy:

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

Aby go użyć:

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

Niedawno zbudowałem ten dodatkowy, który akceptuje ICompare<U>, abyś mógł dostosować porównanie. Przydało się to, gdy musiałem wykonać naturalny ciąg znaków:

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

Wdrożyłem to, działa dobrze. Dodałem parametr „isAscending = true”. Aby posortować w kolejności malejącej, po prostu zamień xiy wokół dwóch metod Invoke (). Dzięki.
Rob L

Jeśli zamierzasz skompilować wyrażenie, równie dobrze możesz po prostu zaakceptować delegata na początek. Ponadto takie podejście ma poważne problemy, jeśli selektor powoduje skutki uboczne, jest kosztowny w obliczeniach lub nie jest deterministyczny. Prawidłowe sortowanie na podstawie wybranego wyrażenia musi wywoływać selektor nie więcej niż raz na pozycję na liście.
Servy

@Servy - to są wszystkie ważne punkty, ale szczerze mówiąc, nie jestem pewien, jak wdrożyć niektóre z twoich sugestii (szczególnie powstrzymując selektor przed powodowaniem skutków ubocznych ...?). Zmieniłem ich na delegatów. Jeśli wiesz, jak wprowadzić niektóre z tych zmian, byłbym szczęśliwy, gdybyś mógł edytować mój kod.
Peter

3
@Peter Nie można zapobiec, aby delegaci powodowali skutki uboczne. Co można zrobić, to upewnić się, że nigdy nie jest wywoływany więcej niż raz za obiekt, to znaczy obliczanie wartości dla każdego obiektu, przechowywania pary obiekt / prognozowanych-wartość, a następnie sortowania że . OrderByrobi to wszystko wewnętrznie.
Servy

Kolejny fajny sposób na napisanie voidmetody rozszerzenia w tym duchu: 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))); } }Można ją uzupełnić SortByDescendingmetodą. Komentarze @ Servy dotyczą również mojego kodu!
Jeppe Stig Nielsen

4

Korzystanie z 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

Ulepszona wersja Rogera.

Problem z GetDynamicSortProperty polega na tym, że pobierają tylko nazwy właściwości, ale co się stanie, jeśli w GridView użyjemy NavigationProperties? wyśle ​​wyjątek, ponieważ znajdzie wartość null.

Przykład:

„Employee.Company.Name;” ulegnie awarii ... ponieważ pozwala tylko „Nazwa” jako parametr uzyskać jego wartość.

Oto ulepszona wersja, która pozwala nam sortować według właściwości nawigacji.

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

Możesz zrobić coś bardziej ogólnego w zakresie wyboru właściwości, a jednocześnie sprecyzować typ, z którego wybierasz, w twoim przypadku „Zamów”:

napisz swoją funkcję jako ogólną:

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

a następnie użyj go w następujący sposób:

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

Możesz być jeszcze bardziej ogólny i zdefiniować typ otwarty dla tego, co chcesz zamówić:

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

i używaj go w ten sam sposób:

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

Co jest głupim, niepotrzebnym, złożonym sposobem robienia „OrderBy” w stylu LINQ, ale może dać ci wskazówkę, jak można go zaimplementować w sposób ogólny


3

Proszę pozwolić mi uzupełnić odpowiedź przez @LukeH z jakimś przykładem kodu, ponieważ go przetestowałem, uważam, że może być przydatny dla niektórych:

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


1

Skorzystaj z LiNQ OrderBy

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

1

W oparciu o moduł porównujący GenericTypeTea :
możemy uzyskać większą elastyczność, dodając flagi sortowania:

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

W tym scenariuszu należy jawnie utworzyć instancję jako MyOrderingClass (zamiast IComparer )
, aby ustawić jej właściwości sortowania:

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

1

Żadna z powyższych odpowiedzi nie była dla mnie wystarczająco ogólna, więc stworzyłem tę:

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

Uważaj jednak na ogromne zbiory danych. Jest to łatwy kod, ale może sprawić kłopoty, jeśli kolekcja jest ogromna, a typ obiektu kolekcji ma dużą liczbę pól. Czas działania to NxM, gdzie:

N = liczba elementów w kolekcji

M = liczba właściwości w obiekcie


1

Każdy, kto pracuje z typami zerowalnymi, Valuejest zobowiązany do korzystania CompareTo.

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


0

Z punktu widzenia wydajności najlepiej jest użyć posortowanej listy, aby dane były sortowane w miarę dodawania do wyniku. Inne podejścia wymagają co najmniej jednej dodatkowej iteracji danych, a większość tworzy kopię danych, co wpłynie nie tylko na wydajność, ale także na użycie pamięci. Może to nie być problem z kilkoma setkami elementów, ale będzie z tysiącami, szczególnie w usługach, w których wiele jednoczesnych żądań może sortować w tym samym czasie. Spójrz na System.Collections.Generic przestrzeń nazw i wybierz klasę z sortowaniem zamiast List.

I jeśli to możliwe, unikaj ogólnych implementacji wykorzystujących refleksję, może to również powodować problemy z wydajnością.


Jak może to być bardziej wydajne? Wstawianie danych do posortowanej listy to O (n), więc dodanie n elementów to O (n ^ 2). Co wiele równoległych elementów próbuje dodać? Są sytuacje, gdy masz dodatkowe cykle procesora do wypalenia podczas dodawania przedmiotów, ale nie jest to dobre ogólne rozwiązanie.
John La Rooy,

0

Załóżmy, że masz następujący kod, w tym kodzie mamy klasę pasażera z kilkoma właściwościami, na podstawie których chcemy posortować.

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

    }
}

Możesz więc zaimplementować swoją strukturę sortowania, używając Delegata składu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.