Як сортувати список <T> за властивістю в об’єкті


1248

У мене є клас з ім'ям , Orderякий має властивості , такі як OrderId, OrderDate, Quantity, і Total. У мене є список цього Orderкласу:

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

Тепер я хочу сортувати список на основі одного властивості Orderоб'єкта, наприклад, мені потрібно сортувати його за датою замовлення або ідентифікатором замовлення.

Як я можу це зробити в C #?


9
Зачекайте - ви хочете сортувати за датою замовлення та ідентифікатором замовлення? Оскільки, здається, більшість відповідей припускають, що ви сортуєте лише те чи інше.
GalacticCowboy

@GalacticCowboy, @GenericTypeTea: Питання говорить "я хочу сортувати список на основі однієї властивості об'єкта" Порядок ", але я згоден, що в інших місцях це не зовсім зрозуміло. Так чи інакше, це не має величезної різниці, чи потрібно вам сортувати за одним або кількома властивостями.
ЛукаХ

@LukeH - Просто перечитав четверте, і я згоден. 'я хочу сортувати список на основі одного властивості' ... і 'дата замовлення або ідентифікатор замовлення'. Між усіма відповідями ми маємо всі бази :)
djdd87

Відповіді:


1807

Найпростіший спосіб, який я можу придумати, - це використовувати Linq:

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

22
як я можу сортувати це у порядку зменшення.
Симпатичний ведмідь

229
Список @BonusKun <Ордер> SortedList = objListOrder. OrderByDescending (o => o.OrderDate) .ToList ();
javajavajaваявая

77
зауважте, що це створює абсолютно новий список з усіма елементами в пам'яті, що може бути проблематично з точки зору продуктивності.
staafl

14
@staafl listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();вистачить для такого починання?
Ендрю Гріндер

28
@staafl Ми замовляємо список посилань на об'єкти, не дублюючи самі об'єкти, наскільки мені відомо. Хоча це робить подвійну пам'ять, використану у списку посилань, це не так вже й погано, як насправді дублювання всіх самих об'єктів, тому в більшості сценаріїв поза межами тих, де ми маємо справу з величезними наборами даних, і зберігання їх у пам'яті вже є проблемою, то його повинно вистачити
Лазар

798

Якщо вам потрібно сортувати список на місці, тоді ви можете скористатися Sortметодом, передавши Comparison<T>делегата:

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

Якщо ви віддаєте перевагу створення нової, відсортованої послідовності, а не сортування на місці, тоді ви можете використовувати OrderByметод LINQ , як згадується в інших відповідях.


30
Так, це "правильна" відповідь, і вона повинна бути набагато ефективнішою, ніж створення нового IEnumerable і перетворення його в новий список.
Джонатан Вуд

45
Звичайно, якщо вам потрібен низхідний сорт, поміняйте місцями xі yправоруч від стрілки =>.
Джеппе Стіг Нільсен

15
Справжня відповідь, яка насправді сортує список за місцем
Мітчелл Керрі

3
@PimBrouwers Це кращий період для варіантів. Навіть якщо об'єм пам'яті, яку ви використовуєте, не є проблемою, це рішення дозволяє уникнути зайвого розподілу пам'яті, що надзвичайно дорого. Цей варіант настільки ж простий кодовий і на порядок швидший.
Cdaragorn

12
@JonSchneider For Nullable<>(візьмемо DateTime?для прикладу), ви можете використовувати, .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))який буде вважати null попереднім усім ненульовим значенням. Це точно так само, як .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Jeppe Stig Nielsen

219

Для цього без LINQ у .Net2.0:

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

Якщо ви перебуваєте на .Net3.0, відповідь LukeH - це те, що ви хочете.

Щоб сортувати за кількома властивостями, ви все одно можете це зробити в межах делегата. Наприклад:

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

Це дасть вам дати зростання за порядком зменшення порядку.

Однак я не рекомендую доклеювати делегатів, оскільки це буде означати багато місць без повторного використання коду. Ви повинні реалізувати IComparerі просто передати це своєму Sortметоду. Дивіться тут .

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

А потім скористатися цим класом IComparer, просто інстанціюйте його та передайте до вашого методу Сортування:

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

1
Відмінна відповідь, і вона повинна бути правильною, оскільки вона захищає повторну ініціалізацію оригінального списку (що завжди буде робити версія LINQ), забезпечуючи кращу інкапсуляцію.
Джеб

@wonton, ні. Ідея полягає в тому, щоб мати можливість здійснити різні IComparerреалізації, надаючи нам поліморфну ​​поведінку.
radarbob

Працювали для мене. Я розмістив версію цього з варіантами сортування.
Джек Гріффін

109

Найпростіший спосіб замовити список - це використовувати OrderBy

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

Якщо ви хочете замовити декілька стовпців, як-от наступний SQL-запит.

ORDER BY OrderDate, OrderId

Для цього ви можете скористатися ThenByнаступним чином.

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

32

Як це було зроблено без Linq:

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

Тоді просто зателефонуйте .sort () у свій список замовлень


3
Спершу потрібно перевірити nullна asакторському складі. Яка вся суть as, як (ha, ha) (Order)objкидає виняток, коли він не вдається. if(orderToCompare == null) return 1;.
radarbob

+1 для використання інтерфейсу, що полегшує підтримку коду та чітко розкриває можливості об'єкта.
AeonOfTime

Якщо Білл і Тед коли-небудь з'являться, я попрошу їх повернути мене до 16 жовтня 2014 року, щоб я могла виправити свою помилку вище - asповертається, nullякщо кастинг не вдасться. Але принаймні нульовий тест є правильним.
radarbob

@radarbob так .. ой. :) Але! Ця функція призначена для автоматичного використання за допомогою сортування за списком, тобто List<Order>тип повинен гарантувати відповідність дляas тому 2014, ви, ймовірно, не написали помилку, настільки, щоб уникнути зайвої заяви охоронця :)
Джиммі Хоффа

слід гарантувати Цікавий момент. Якщо він добре інкапсульований таким чином, що його не можна викликати, окрім як пропустити a List<Order>; але ми з вами зустрілися з самозакоханим програмістом, який невисловлював презумпцію: "Я пишу цей код, щоб він не використовувався неправильно"
radarbob

26

Класичне об'єктно-орієнтоване рішення

По-перше, я повинен поглянути на дивовижність LINQ .... Тепер, коли ми це вийшли з шляху

Варіація на відповідь Джиммі Хоффи. З дженериками CompareToпараметр стає типом безпечним.

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

Ця сортування за замовчуванням звичайно може бути повторно використана. Тобто кожен клієнт не повинен зайво переписувати логіку сортування. Заміна "1" і "-1" (або логічні оператори, ваш вибір) скасовує порядок сортування.


Простий підхід для сортування об'єктів у списку. Але я не розумію, чому ви повертаєте 1, якщо (що == null)?
Loc Huynh

4
це означає, що thisоб'єкт більший від нуля. Для цілей сортування нульове посилання на об'єкт "менше" this. Ось тільки я вирішив визначити, як сортуватимуть нулі.
radarbob

18

// Повністю загальне сортування для використання з сіткою

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
Або, ви знаєте, data.OrderBy(). Трохи простіше, ніж переосмислити колесо.
gunr2171

4
Ідея така - це буде працювати з будь-яким типом об’єктів. Де як OrderBy () працює лише з сильно набраними об'єктами.
roger

1
Думаю, багато. На мою думку, найпростіше та зручне рішення для сортування даних сітки!
костас ч.

6

Ось загальний метод розширення LINQ, який не створює зайвої копії списку:

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

Щоб використовувати його:

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

Нещодавно я створив цей додатковий, який приймає ICompare<U>, так що ви можете налаштувати порівняння. Це стане в нагоді, коли мені потрібно було зробити сортування Natural String:

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

Я це реалізував, працює чудово. Я додав параметр "isAscending = true". Щоб сортувати у порядку зменшення, просто поміняйте місцями x і y навколо двох методів Invoke (). Дякую.
Роб Л

Якщо ви просто збираєтеся скласти вираз, ви можете просто прийняти делегата для початку. Крім того, такий підхід має великі проблеми, якщо селектор викликає побічні ефекти, дорого обчислюється або не є детермінованим. Для правильного сортування на основі вибраного виразу потрібно викликати селектор не більше одного разу на елемент у списку.
Сервіс

@ Сервіс - це все достовірні моменти, але я чесно кажу, я не впевнений, як реалізувати деякі ваші пропозиції (особливо зупиняючи вибір, щоб викликати побічні ефекти ...?). Я змінив їх на делегатів. Якщо ви знаєте, як внести деякі з цих змін, я би радий вам редагувати мій код.
Петро

3
@Peter Ви не можете перешкодити делегатам викликати побічні ефекти. Що ви можете зробити , це переконатися , вони ніколи не називали більш ніж один раз для кожного об'єкта, це означає обчислювальні значення для кожного об'єкта, який зберігає пар об'єкта / проектованого-значення, а потім сортування що . OrderByробить все це внутрішньо.
Сервіс

Ще один приємний спосіб написати voidметод розширення в такому дусі: 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))); } }Може бути доповнений SortByDescendingметодом. @ Коментарі Серві також стосуються мого коду!
Джеппе Стіг Нільсен


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

Удосконалена версія Роджера.

Проблема з GetDynamicSortProperty полягає в тому, що отримують лише імена властивостей, але що відбувається, якщо в GridView ми використовуємо NavigationProperties? він надішле виняток, оскільки визнає нульовим.

Приклад:

"Employee.Company.Name;" вийде з ладу ... оскільки дозволяє лише "Ім'я" як параметр отримати значення.

Ось покращена версія, яка дозволяє сортувати за навігаційними властивостями.

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

Ви можете зробити щось більш загальне щодо вибору властивостей, але конкретно стосовно типу, який ви вибираєте, у вашому випадку "Замовити":

написати свою функцію як загальну:

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

а потім використовуйте його так:

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

Ви можете бути ще більш загальним і визначити відкритий тип для того, що ви хочете замовити:

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

і використовувати його так само:

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

Що є дурним непотрібним складним способом створення стилю LINQ "OrderBy", але він може дати вам зрозумілі шляхи, як це можна реалізувати в загальному вигляді


3

Будь ласка, дозвольте мені доповнити відповідь @LukeH з деяким зразком коду, тому що я перевірив це, я вважаю, що він може бути корисним для деяких:

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

На основі порівняння GenericTypeTea :
ми можемо отримати більшу гнучкість, додавши сортувальні прапори:

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

У цьому сценарії ви повинні створити його як MyOrderingClass явно (а не IComparer )
, щоб встановити його властивості сортування:

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

1

Жоден із наведених відповідей для мене був недостатньо загальним, тому я зробив цю:

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

Але обережно щодо масивних наборів даних. Це простий код, але може заважати вам, якщо колекція величезна, а тип об’єкта колекції має велику кількість полів. Час виконання NxM, де:

N = # елементів у колекції

M = # властивостей у об’єкті


1

Усі, хто працює з змінними типами, Valueповинні використовувати CompareTo.

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


0

З точки зору продуктивності найкраще використовувати відсортований список так, щоб дані сортувались по мірі додавання до результату. Інші підходи потребують принаймні однієї додаткової ітерації даних, а більшість створює копію даних, щоб вплинути не тільки на продуктивність, але й на використання пам'яті. Можливо, це не проблема з кількома сотнями елементів, але буде з тисячами, особливо в сервісах, де багато одночасних запитів можуть сортувати одночасно. Погляньте на System.Collections.Generic простір імен та виберіть клас із сортуванням замість List.

І уникайте загальних реалізацій, де можливо, відображення, це також може спричинити проблеми з продуктивністю.


Як це може бути ефективнішим? Вставлення даних до відсортованого списку дорівнює O (n), тому додавання n елементів - O (n ^ 2). Які багато спільних елементів намагаються додати? Бувають ситуації, коли у вас є додаткові цикли процесора для запису під час додавання елементів, але це не є хорошим загальним рішенням.
Джон Ла Рой

0

Припустимо, у вас є наступний код, у цьому коді у нас є клас Пасажир з парою властивостей, за якими ми хочемо сортувати на основі.

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

    }
}

Таким чином, ви можете реалізувати структуру сортування за допомогою делегата Composition.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.