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

937

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

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

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

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

  • 6
    Подождите , - вы хотите сортировать как дату заказа и идентификатор заказа? Потому что большинство ответов, похоже, предполагают, что вы сортируете только по одному или другому.
  • 0
    @GalacticCowboy, @GenericTypeTea: Вопрос говорит: «Я хочу отсортировать список на основе одного свойства объекта Order», но я согласен, что в других местах это не совсем понятно. В любом случае, не имеет большого значения, нужно ли вам сортировать по одному или нескольким свойствам.
Показать ещё 2 комментария
Теги:
generics
list
sorting

19 ответов

1445
Лучший ответ

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

List<Order> SortedList = objListOrder.OrderBy(o=>o.OrderDate).ToList();
  • 15
    как я могу отсортировать это в порядке убывания.
  • 182
    @BonusKun List <Order> SortedList = objListOrder. OrderByDescending (o => o.OrderDate) .ToList ();
Показать ещё 16 комментариев
527

Если вам нужно отсортировать список на месте, вы можете использовать метод Sort, передав Comparison<T> delegate:

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

Если вы предпочитаете создавать новую, отсортированную последовательность, а не сортировать на месте, вы можете использовать метод LINQ OrderBy, как указано в других ответах.

  • 1
    Если ваши поля даты имеют тип Nullable DateTime (т.е. DateTime?), И вы получаете ошибку компиляции, такую как «Невозможно преобразовать лямбда-выражение в тип« System.Collections.Generic.IComparer <...> », потому что это не тип делегата msgstr ", реальная проблема может заключаться в том, что нет метода CompareTo для обнуляемого типа DateTime ?. Если вы уверены, что объект не будет нулевым, вы можете привести его к обычному DateTime, чтобы решить проблему.
  • 17
    Да, это «правильный» ответ, и он должен быть намного эффективнее, чем создание нового IEnumerable и его преобразование в новый список.
Показать ещё 6 комментариев
212

Для этого без LINQ на .Net2.0:

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

Если вы находитесь на .Net3.0, то LukeH answer - это то, что вам нужно.

Чтобы отсортировать несколько свойств, вы все равно можете сделать это в пределах делегата. Например:

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

Это даст вам возрастающие даты с убывающим orderIds.

Однако я бы не рекомендовал придерживаться делегатов, поскольку это будет означать много мест без повторного использования кода. Вы должны реализовать 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);
  • 0
    Было бы хорошей идеей использовать синглтон для сравнения?
  • 0
    Отличный ответ, и он должен быть правильным, поскольку он защищает повторную инициализацию исходного списка (что всегда будет делать версия LINQ), обеспечивая лучшую инкапсуляцию.
Показать ещё 2 комментария
79

OrderBy способ заказать список - использовать OrderBy

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

Если вы хотите заказать несколько столбцов, например, следующий SQL Query.

ORDER BY OrderDate, OrderId

Для этого вы можете использовать ThenBy как ThenBy ниже.

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

Выполнение этого без 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 , как и (ха, ха) (Order)obj бросает исключение , когда он выходит из строя. if(orderToCompare == null) return 1; ,
  • 0
    +1 за использование интерфейса, который облегчает поддержку кода и четко раскрывает возможности объекта.
Показать ещё 3 комментария
20

Классическое объектно-ориентированное решение

Сначала я должен поклониться удивительности LINQ... Теперь, когда у нас это получилось,

Вариант ответа Джимми Хоффа. С generics параметр 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" (или логические операторы, ваш выбор) отменяет порядок сортировки.

  • 0
    Простой подход для сортировки объектов в списке. Но я не понимаю, почему вы возвращаете 1, если (что == ноль)?
  • 2
    это означает, что this объект больше нуля. В целях сортировки ссылка на нулевой объект «меньше» this объекта. Именно так я решил определить, как будут сортироваться нули.
15

//Полностью общая сортировка для использования с 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() . Немного проще, чем заново изобретать колесо.
  • 4
    Идея в том, что это будет работать с любым типом объекта. Где OrderBy () работает только со строго типизированными объектами.
Показать ещё 1 комментарий
5

Вот общий метод расширения 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>, так что вы можете настроить сравнение. Это пригодилось, когда мне нужно было сделать естественную сортировку строк:

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)));
}
  • 0
    Я реализовал это, работает отлично. Я добавил параметр isAscending = true. Чтобы отсортировать по убыванию, просто поменяйте местами x и y внутри двух методов Invoke (). Благодарю.
  • 0
    Если вы просто собираетесь скомпилировать выражение, вы можете просто принять делегата для начала. Кроме того, этот подход имеет серьезные проблемы, если селектор вызывает побочные эффекты, является дорогостоящим для вычисления или не является детерминированным. Правильная сортировка на основе выбранного выражения должна вызывать селектор не более одного раза для каждого элемента в списке.
Показать ещё 3 комментария
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 ...
}
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

Улучшена версия Roger.

Проблема с 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
//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

Использование LINQ

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

objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderId)
                   .ToList();
2
var obj = db.Items.Where...

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

Ни один из вышеперечисленных ответов не был достаточно общим для меня, поэтому я сделал это:

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

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

Использовать LiNQ OrderBy

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

Для использования CompareTo требуется <Кто-нибудь, кто работает с типами с нулевым значением, Value.

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

0

С точки зрения производительности лучше всего использовать отсортированный список, чтобы данные сортировались по мере добавления к результату. Другие подходы нуждаются, по крайней мере, в одной дополнительной итерации по данным, и большинство из них создают копию данных, поэтому будет затронута не только производительность, но и использование памяти. Не может быть проблемой с несколькими сотнями элементов, но будет с тысячами, особенно в тех службах, где одновременно может выполняться сортировка нескольких параллельных запросов. Посмотрите пространство имен System.Collections.Generic и выберите класс с сортировкой вместо списка.

И избегайте общих реализаций, используя отражение, когда это возможно, это также может вызвать проблемы с производительностью.

  • 0
    Как это может быть более производительным? Вставка данных в отсортированный список - это O (n), поэтому добавление n элементов - это O (n ^ 2). Что это много параллельных предметов пытаются добавить? Есть ситуации, когда у вас есть дополнительные циклы ЦП для записи во время добавления элементов, но это не очень хорошее общее решение.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню