Как отсортировать список <T> по свойству объекта


1252

У меня есть класс с именем , Orderкоторый имеет свойства , такие как OrderId, OrderDate, Quantity, и Total. У меня есть список этого Orderкласса:

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

Теперь я хочу отсортировать список по одному свойству Orderобъекта, например, мне нужно отсортировать его по дате заказа или идентификатору заказа.

Как я могу сделать это в C #?


9
Подождите , - вы хотите сортировать как дату заказа и идентификатор заказа? Потому что большинство ответов, похоже, предполагают, что вы сортируете только по одному или другому.
GalacticCowboy

@GalacticCowboy, @GenericTypeTea: Вопрос говорит: «Я хочу отсортировать список на основе одного свойства объекта Order», но я согласен, что в других местах это не совсем понятно. В любом случае, не имеет большого значения, нужно ли вам сортировать по одному или нескольким свойствам.
ЛукиH

@LukeH - Только что перечитал, и я согласен. «Я хочу отсортировать список на основе одного свойства» ... и «Дата заказа или идентификатор заказа». Между всеми ответами у нас есть все основания покрыты :).
djdd87

Ответы:


1811

Самый простой способ, который я могу придумать, это использовать Linq:

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

22
как я могу отсортировать это в порядке убывания.
Милый медведь

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

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 Для Nullable<>(возьмем DateTime?в качестве примера) вы можете использовать, .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))который будет обрабатывать нуль как предшествующий всем ненулевым значениям. Это точно так же, как .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Джеппе Стиг Нильсен

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, просто создайте его экземпляр и передайте его в свой метод Sort:

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

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

@ Вонтон, нет. Идея состоит в том, чтобы иметь возможность иметь различные IComparerреализации, дающие нам полиморфное поведение.
радаробоб

Работал на меня. Я разместил версию этого с опциями сортировки.
Джек Гриффин

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

Делать это без Линка, как вы сказали:

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, как (ха, ха) (Order)objвыдает исключение, когда оно терпит неудачу. if(orderToCompare == null) return 1;,
радаробоб

+1 за использование интерфейса, что облегчает поддержку кода и четко раскрывает возможности объекта.
AeonOfTime

Если Билл и Тед когда-нибудь появятся, я попрошу их перенести меня обратно на 16 октября 2014 года, чтобы я мог исправить мою ошибку выше - asвернется, nullесли произойдет сбой. Но по крайней мере нулевой тест верен.
радаробоб

@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, если (что == ноль)?
Loc Huynh

4
это означает, что thisобъект больше нуля. В целях сортировки нулевая ссылка на объект "меньше чем" thisобъект. Именно так я решил определить, как будут сортироваться нули.
радаробоб

18

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

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 () работает только со строго типизированными объектами.
Роджер

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:

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 (). Спасибо.
Роб Л

Если вы просто собираетесь скомпилировать выражение, вы можете просто принять делегата для начала. Кроме того, этот подход имеет серьезные проблемы, если селектор вызывает побочные эффекты, является дорогостоящим для вычисления или не является детерминированным. Правильная сортировка на основе выбранного выражения должна вызывать селектор не более одного раза для каждого элемента в списке.
Servy

@Servy - это все верные аргументы, но я буду честен, я не уверен, как реализовать некоторые из ваших предположений (особенно, чтобы не вызывать селектор от побочных эффектов ...?). Я изменил их на делегатов. Если вы знаете, как внести некоторые из этих изменений, я буду рад, если вы отредактируете мой код.
Питер

3
@ Петр Вы не можете предотвратить делегатов от побочных эффектов. Что вы можете сделать , это убедиться , они никогда не называли более чем один раз для каждого объекта, это означает вычислительные значения для каждого объекта, хранящий пар объекта / проецируемого-значение, а затем сортировка что . OrderByделает все это внутренне.
Servy

Еще один хороший способ написать 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методом. Комментарии @ Servy также применимы и к моему коду!
Джеппе Стиг Нильсен


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;" завершится сбоем ..., так как позволяет только "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

Вы можете сделать что-то более общее с выбором свойств, но быть конкретным с типом, из которого вы выбираете, в вашем случае 'Order':

напишите свою функцию как общую:

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

Это глупый ненужный сложный способ выполнения стиля OrderQy в LINQ, но он может дать вам представление о том, как его можно реализовать в общем виде.


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

На основе Comparer 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

Предположим, у вас есть следующий код, в этом коде у нас есть класс Passenger с парой свойств, которые мы хотим отсортировать на основе.

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.