Como classificar uma lista <T> por uma propriedade no objeto


1249

Eu tenho uma classe chamada Orderque tem propriedades tais como OrderId, OrderDate, Quantity, e Total. Eu tenho uma lista desta Orderclasse:

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

Agora, quero classificar a lista com base em uma propriedade do Orderobjeto, por exemplo, preciso classificá-la pela data do pedido ou pelo ID do pedido.

Como posso fazer isso em c #?


9
Aguarde - você deseja classificar por data e ID do pedido? Porque a maioria das respostas parece assumir que você está classificando apenas uma ou outra.
GalacticCowboy

@GalacticCowboy, @GenericTypeTea: A pergunta diz "eu quero classificar a lista com base em uma propriedade do objeto Order", mas concordo que em outros lugares não é completamente claro. De qualquer forma, não faz muita diferença se você precisa classificar por uma ou várias propriedades.
LukeH

@LukeH - Acabei de fazer uma releitura e concordo. 'quero classificar a lista com base em uma propriedade' ... e 'data do pedido ou ID do pedido'. Entre todas as respostas, temos todas as bases cobertas :).
Djdd87

Respostas:


1807

A maneira mais fácil de pensar é usar o Linq:

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

22
como posso classificar isso em ordem decrescente.
Urso bonito

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

77
observe que isso cria uma lista totalmente nova com todos os itens da memória, o que pode ser problemático em termos de desempenho.
staafl

14
@staafl será listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();suficiente para tal empreendimento?
Andrew Grinder

28
@staafl Estamos solicitando uma lista de referências a objetos, e não duplicando os próprios objetos, tanto quanto sei. Embora isso duplique a memória usada pela lista de referências, não é tão ruim quanto duplicar todos os objetos em si; portanto, na maioria dos cenários, fora daqueles onde estamos lidando com grandes conjuntos de dados e mantendo-os na memória, isso já é um problema. deve ser suficiente.
Lázaro

798

Se você precisar classificar a lista no local, poderá usar o Sortmétodo, passando um Comparison<T>delegado:

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

Se você preferir criar uma nova seqüência classificada, em vez de classificar no local, poderá usar o OrderBymétodo LINQ , conforme mencionado nas outras respostas.


30
Sim, esta é a resposta "correta" e deve ser muito mais eficiente do que criar um novo IEnumerable e depois convertê-lo em uma nova lista.
Jonathan Wood

45
Obviamente, se você precisar de ordenação descendente, troque xe yno lado direito da seta =>.
Jeppe Stig Nielsen

15
A verdadeira resposta que realmente classifica a lista no local
Mitchell Currie

3
@PimBrouwers Este é o melhor período para opções. Mesmo que a quantidade de memória que você está usando não seja um problema, esta solução evita a alocação desnecessária de memória, que é EXTREMAMENTE cara. Essa opção é tão simples quanto em código e uma ordem de magnitude mais rápida.
Cdaragorn

12
@ JonSchneider Para Nullable<>(considere DateTime?o exemplo), você pode usar o .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))que tratará nulo como precedendo todos os valores não nulos. É exatamente o mesmo que .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Jeppe Stig Nielsen

219

Para fazer isso sem o LINQ no .Net2.0:

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

Se você está no .Net3.0, a resposta de LukeH é o que você procura .

Para classificar em várias propriedades, você ainda pode fazê-lo em um delegado. Por exemplo:

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

Isso forneceria datas ascendentes com orderIds decrescente .

No entanto, eu não recomendaria a adesão de delegados, pois isso significa muitos lugares sem reutilização de código. Você deve implementar IComparere passar isso ao seu Sortmétodo. Veja aqui .

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 então, para usar essa classe IComparer, instancie-a e passe-a para o seu método Sort:

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

1
Excelente resposta e deve ser a correta, pois protege a reinicialização da lista original (que a versão do LINQ sempre fará), proporcionando um melhor encapsulamento.
Jeb

@wonton, não. A idéia é poder ter diferentes IComparerimplementações, dando-nos um comportamento polimórfico.
Radarbob # 16/14

Trabalhou para mim. Publiquei uma versão disso com opções de classificação.
Jack Griffin

109

A maneira mais simples de solicitar uma lista é usar OrderBy

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

Se você deseja solicitar várias colunas, como segue a Consulta SQL.

ORDER BY OrderDate, OrderId

Para conseguir isso, você pode usar o ThenByseguinte.

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

32

Fazendo isso sem o Linq, como você disse:

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

Em seguida, basta chamar .sort () na sua lista de pedidos


3
É preciso primeiro testar nullno aselenco. Qual é o objetivo de as, pois (ha, ha) (Order)objlança uma exceção quando falha. if(orderToCompare == null) return 1;.
Radarbob

+1 por usar a interface, que facilita a manutenção do código e expõe claramente os recursos do objeto.
AeonOfTime 6/11

Se Bill e Ted aparecerem, pedirei que me levem de volta a 16 de outubro de 2014 para que eu possa corrigir meu erro acima - asretorna nullse o elenco falhar. Mas pelo menos o teste nulo está certo.
Radarbob #

@radarbob yeah .. oops. :) Mas! Esta função deve ser usada automaticamente por uma classificação em uma lista que tenha List<Order>a garantia de que o tipo corresponda no ano de as2014. Você provavelmente não escreveu um bug, além de evitar uma declaração de guarda desnecessária :)
Jimmy Hoffa

deve ser garantido ponto interessante. Se estiver bem encapsulado de forma que não possa ser chamado, exceto para passar a List<Order>; mas você e eu conheci o programador auto-encapsulado que é presunção tácita é "eu estou escrevendo este código para que ele não será errado usado"
radarbob

26

Uma solução clássica orientada a objetos

Primeiro, devo me associar à grandiosidade do LINQ .... Agora que temos isso fora do caminho

Uma variação na resposta JimmyHoffa. Com genéricos, o CompareToparâmetro passa a ser do tipo seguro.

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

Essa classificação padrão é reutilizável, é claro. Ou seja, cada cliente não precisa redundantemente reescrever a lógica de classificação. Trocar o "1" e o "-1" (ou os operadores lógicos, sua escolha) reverte a ordem de classificação.


Abordagem simples para classificar objetos em uma lista. Mas eu não entendo por que você retorna 1 se (que == nulo)?
precisa

4
significa que o thisobjeto é maior que nulo. Para fins de classificação, uma referência de objeto nulo "é menor que" thisobjeto. Foi assim que decidi definir como os nulos serão classificados.
Radarbob

18

// Classificação totalmente genérica para uso com um 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
Ou você sabe data.OrderBy(). Um pouco mais fácil do que reinventar a roda.
gunr2171

4
A idéia é que isso funcione com qualquer tipo de objeto. Onde OrderBy () funciona apenas com objetos fortemente tipados.
roger

1
Na minha opinião, a solução mais simples e utilizável para classificar os dados da grade!
kostas ch.

6

Aqui está um método de extensão LINQ genérico que não cria uma cópia extra da lista:

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

Para usá-lo:

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

Recentemente, criei este adicional que aceita um ICompare<U>, para que você possa personalizar a comparação. Isso veio a calhar quando eu precisava fazer uma ordenação 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)));
}

Eu implementei isso, funciona bem. Eu adicionei um parâmetro "isAscending = true". Para classificar em ordem decrescente, basta trocar x e y nos dois métodos Invoke (). Obrigado.
Rob L

Se você apenas compilar a expressão, poderá aceitar um delegado para começar. Além disso, essa abordagem apresenta grandes problemas se o seletor causar efeitos colaterais, custar caro na computação ou não for determinístico. Uma classificação adequada com base em uma expressão selecionada precisa chamar o seletor não mais que uma vez por item na lista.
Servy

@Servy - esses são todos os pontos válidos, mas vou ser sincero, não sei como implementar algumas de suas sugestões (especialmente parar um seletor de causar efeitos colaterais ...?). Eu mudei para delegados. Se você souber fazer algumas dessas alterações, ficarei feliz em editar o meu código.
Peter

3
@ Peter Você não pode impedir que os delegados causem efeitos colaterais. O que você pode fazer é garantir que eles nunca está chamado mais de uma vez por objeto, isto significa computar os valores para cada objeto, armazenando os pares objeto /-valor projetado, e depois classificar que . OrderByfaz tudo isso internamente.
Servy

Outra boa maneira de escrever um voidmétodo de extensão nesse espírito: 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))); } }pode ser complementado por um SortByDescendingmétodo. Os comentários do @ Servy também se aplicam ao meu código!
Jeppe Stig Nielsen

4

Usando 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

Uma melhoria da versão de Roger.

O problema com GetDynamicSortProperty é que apenas obtemos os nomes das propriedades, mas o que acontece se no GridView usarmos NavigationProperties? ele enviará uma exceção, pois é nulo.

Exemplo:

"Employee.Company.Name;" falhará ... pois permite que apenas "Name" como parâmetro obtenha seu valor.

Aqui está uma versão aprimorada que nos permite classificar por Propriedades de navegação.

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

Você pode fazer algo mais genérico sobre a seleção de propriedades e ainda ser específico sobre o tipo de sua escolha, no seu caso 'Pedido':

escreva sua função como genérica:

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

e depois use-o assim:

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

Você pode ser ainda mais genérico e definir um tipo aberto para o que deseja solicitar:

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 use da mesma maneira:

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

O que é uma maneira estúpida e desnecessária e complexa de executar um estilo LINQ 'OrderBy', mas pode fornecer uma pista de como ele pode ser implementado de maneira genérica


3

Deixe-me completar a resposta de @LukeH com algum código de exemplo, pois eu o testei e acredito que possa ser útil para alguns:

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

Faça uso do LiNQ OrderBy

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

1

Baseado no comparador de GenericTypeTea :
podemos obter mais flexibilidade adicionando sinalizadores de classificação:

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

Nesse cenário, você deve instancia-lo como MyOrderingClass explicitamente (em vez de IComparer )
para definir suas propriedades de classificação:

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

1

Nenhuma das respostas acima foi genérica o suficiente para mim, então eu fiz esta:

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

Cuidado com conjuntos de dados massivos. É um código fácil, mas pode causar problemas se a coleção for enorme e o tipo de objeto da coleção tiver um grande número de campos. O tempo de execução é NxM, onde:

N = Nº de elementos na coleção

M = Nº de propriedades no objeto


1

Qualquer pessoa que trabalhe com tipos anuláveis Valuedeve usar CompareTo.

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


0

Do ponto de vista do desempenho, o melhor é usar uma lista classificada para que os dados sejam classificados conforme são adicionados ao resultado. Outras abordagens precisam de pelo menos uma iteração extra nos dados e a maioria cria uma cópia dos dados, para não apenas o desempenho, mas também o uso da memória. Pode não haver um problema com algumas centenas de elementos, mas estará com milhares, especialmente em serviços onde muitas solicitações simultâneas podem fazer a classificação ao mesmo tempo. Dê uma olhada no espaço para nome System.Collections.Generic e escolha uma classe com classificação em vez de Lista.

E evite implementações genéricas usando reflexão sempre que possível, isso também pode causar problemas de desempenho.


Como isso pode ser mais eficiente? A inserção de dados em uma lista classificada é O (n), portanto, a adição de n itens é O (n ^ 2). O que muitos itens simultâneos estão tentando ser adicionados? Há situações em que você tem ciclos extras de CPU para gravar enquanto itens são adicionados, mas essa não é uma boa solução geral.
John La Rooy

0

Suponha que você tenha o código a seguir; nesse código, temos uma classe Passenger com algumas propriedades nas quais queremos classificar com 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);

    }
}

Portanto, você pode implementar sua estrutura de classificação usando o delegado Composition.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.