Cómo ordenar una lista <T> por una propiedad en el objeto

1250

Tengo una clase llamada Orderque tiene propiedades tales como OrderId, OrderDate, Quantity, y Total. Tengo una lista de esta Orderclase:

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

Ahora quiero ordenar la lista en función de una propiedad del Orderobjeto, por ejemplo, necesito ordenarla por la fecha del pedido o el ID del pedido.

¿Cómo puedo hacer esto en C #?

Shyju
fuente
99
Espere: ¿desea ordenar por fecha de pedido y por ID de pedido? Porque la mayoría de las respuestas parecen suponer que solo estás ordenando por una u otra.
GalacticCowboy
@GalacticCowboy, @GenericTypeTea: La pregunta dice "quiero ordenar la lista en función de una propiedad del objeto Order", pero estoy de acuerdo en que en otros lugares no está completamente claro. De cualquier manera, no hace una gran diferencia si necesita ordenar por una o varias propiedades.
LukeH
@LukeH: acabo de volver a leer y estoy de acuerdo. 'Quiero ordenar la lista en función de una propiedad' ... y 'fecha de pedido o id de pedido'. Entre todas las respuestas, tenemos todas las bases cubiertas :).
djdd87

Respuestas:

1808

La forma más fácil que se me ocurre es usar Linq:

List<Order> SortedList = objListOrder.OrderBy(o=>o.OrderDate).ToList();
Lázaro
fuente
22
¿Cómo puedo ordenar esto en orden descendente?
Cute Bear
229
@BonusKun List <Order> SortedList = objListOrder. OrderByDescending (o => o.OrderDate) .ToList ();
javajavajavajavajava
77
Tenga en cuenta que esto crea una lista completamente nueva con todos los elementos en la memoria, lo que puede ser problemático en términos de rendimiento.
staafl
14
@staafl será listWithObjects = listWithObjects.OrderByDescending(o => o.Status).ToList();suficiente para tal esfuerzo?
Andrew Grinder
28
@staafl Estamos ordenando una lista de referencias de objetos, no duplicando los objetos mismos hasta donde yo sé. Si bien esto duplica la memoria utilizada por la lista de referencias, no es tan malo como duplicar todos los objetos en sí, por lo que en la mayoría de los escenarios fuera de aquellos en los que estamos tratando con grandes conjuntos de datos y mantenerlos en la memoria ya es un problema, entonces Debería ser suficiente.
Lázaro
798

Si necesita ordenar la lista en el lugar, puede usar el Sortmétodo, pasando un Comparison<T>delegado:

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

Si prefiere crear una nueva secuencia ordenada en lugar de ordenar en el lugar, puede usar el OrderBymétodo de LINQ , como se menciona en las otras respuestas.

LukeH
fuente
30
Sí, esta es la respuesta "correcta" y debería ser mucho más eficiente que crear un nuevo IEnumerable y luego convertirlo en una nueva lista.
Jonathan Wood
45
Por supuesto, si necesita una clasificación descendente, intercambie xy yen el lado derecho de la flecha =>.
Jeppe Stig Nielsen
15
La verdadera respuesta que realmente ordena la lista en el lugar
Mitchell Currie el
3
@PimBrouwers Este es el mejor período de opción. Incluso si la cantidad de memoria que está utilizando no es un problema, esta solución evita la asignación innecesaria de memoria que es EXTREMADAMENTE costosa. Esta opción es tan simple en código y un orden de magnitud más rápido.
Cdaragorn
12
@JonSchneider For Nullable<>(tome DateTime?como ejemplo) puede usar el .Sort((x, y) => Nullable.Compare(x.OrderDate, y.OrderDate))cual tratará nulo como precedente a todos los valores no nulos. Es exactamente lo mismo que .Sort((x, y) => Comparer<DateTime?>.Default.Compare(x.OrderDate, y.OrderDate).
Jeppe Stig Nielsen
219

Para hacer esto sin LINQ en .Net2.0:

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

Si estás en .Net3.0, entonces la respuesta de LukeH es lo que buscas.

Para ordenar múltiples propiedades, aún puede hacerlo dentro de un delegado. Por ejemplo:

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

Esto le daría fechas ascendentes con órdenes descendentes .

Sin embargo, no recomendaría delegar a los delegados, ya que significará muchos lugares sin reutilización de código. Debe implementar un IComparery simplemente pasarlo a su Sortmétodo. Ver aquí .

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

Y luego, para usar esta clase de IComparer, simplemente ejemplifique y pase a su método Sort:

IComparer<Order> comparer = new MyOrderingClass();
orderList.Sort(comparer);
djdd87
fuente
1
Excelente respuesta, y debería ser la correcta, ya que protege la reinicialización de la lista original (que la versión LINQ siempre hará) proporcionando una mejor encapsulación.
Jeb
@wonton, no. La idea es poder tener diferentes IComparerimplementaciones, dándonos un comportamiento polimórfico.
radarbob
Trabajó para mi. Publiqué una versión de esto con opciones de clasificación.
Jack Griffin
109

La manera más simple de ordenar una lista es usar OrderBy

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

Si desea ordenar por varias columnas como seguir la consulta SQL.

ORDER BY OrderDate, OrderId

Para lograr esto, puede usar lo ThenBysiguiente.

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

Haciéndolo sin Linq como dijiste:

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

Luego simplemente llame a .sort () en su lista de Órdenes

Jimmy Hoffa
fuente
3
Primero hay que probar nullen el aselenco. De eso se trata as, ya que (ja, ja) (Order)objlanza una excepción cuando falla. if(orderToCompare == null) return 1;.
radarbob
+1 por usar la interfaz, lo que hace que el código sea más fácil de mantener y expone claramente las capacidades del objeto.
AeonOfTime
Si Bill y Ted aparecen alguna vez, les pediré que me lleven de vuelta al 16 de octubre de 2014 para poder corregir mi error anterior, asvuelve nullsi el elenco falla. Pero al menos la prueba nula es correcta.
radarbob
@radarbob, sí ... ¡Vaya! :) ¡Pero! Esta función está destinada a ser utilizada automáticamente por una clasificación en una lista, por lo que List<Order>debe garantizarse que el tipo coincida en el as2014, por lo que probablemente no escribió un error, tanto como para evitar una declaración de guardia innecesaria :)
Jimmy Hoffa
debe garantizarse Punto interesante. Si está bien encapsulado de modo que no se pueda invocar, excepto para pasar a List<Order>; pero tú y yo nos hemos encontrado con el programador
autoencapsulado cuya suposición implícita
26

Una solución clásica orientada a objetos

Primero debo hacer una genuflexión a la genialidad de LINQ ... Ahora que lo hemos quitado del camino

Una variación de la respuesta JimmyHoffa. Con los genéricos, el CompareToparámetro se convierte en 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(); 

Esta capacidad de clasificación predeterminada es reutilizable, por supuesto. Es decir, cada cliente no tiene que volver a escribir de forma redundante la lógica de clasificación. Intercambiar el "1" y el "-1" (o los operadores lógicos, según su elección) invierte el orden de clasificación.

radarbob
fuente
Enfoque simple para ordenar objetos en una Lista. Pero no entiendo por qué devuelve 1 if (that == null)?
Loc Huynh
44
significa que el thisobjeto es mayor que nulo. Para fines de clasificación, una referencia de objeto nulo "es menor que" thisobjeto. Así es como decidí definir cómo se ordenarán los nulos.
radarbob
18

// Clasificación totalmente genérica para usar con una vista de cuadrícula

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);
    }
Roger
fuente
44
O ya sabes data.OrderBy(). Un poco más fácil que reinventar la rueda.
gunr2171
44
La idea es que esto funcionará con cualquier tipo de objeto. Donde como OrderBy () solo funciona con objetos fuertemente tipados.
Roger
1
Muchas gracias. En mi opinión, ¡la solución más simple y útil para clasificar datos de cuadrícula!
kostas ch.
6

Aquí hay un método genérico de extensión LINQ que no crea una copia adicional de la 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 usarlo:

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

Recientemente construí este adicional que acepta un ICompare<U>, para que pueda personalizar la comparación. Esto fue útil cuando necesitaba hacer un tipo de cadena 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)));
}
Peter
fuente
He implementado esto, funciona bien. Agregué un parámetro "isAscending = true". Para ordenar en orden descendente, simplemente cambie las x e y alrededor dentro de los dos métodos Invoke (). Gracias.
Rob L
Si solo va a compilar la expresión, también puede aceptar un delegado para empezar. Además, este enfoque tiene problemas importantes si el selector causa efectos secundarios, es costoso de calcular o no es determinista. Una ordenación adecuada basada en una expresión seleccionada necesita invocar el selector no más de una vez por elemento en la lista.
Servy
@Servy: todos esos son puntos válidos, pero seré sincero, no estoy seguro de cómo implementar algunas de sus sugerencias (¿especialmente evitar que un selector cause efectos secundarios ...?). Los he cambiado a delegados. Si sabe cómo hacer algunos de esos cambios, me complacería que edite mi código.
Peter
3
@ Peter No puede evitar que los delegados causen efectos secundarios. Lo que puede hacer es asegurarse de que nunca se les llama más de una vez por objeto, esto significa que el cálculo de los valores de cada objeto, que almacenan los pares de objetos / valor proyectadas, y luego clasificando que . OrderByhace todo eso internamente.
Servy
Otra buena manera de escribir un voidmétodo de extensión con este espíritu: 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))); } }puede complementarse con un SortByDescendingmétodo. ¡Los comentarios de @ Servy también se aplican a mi código!
Jeppe Stig Nielsen
4

Usando LINQ

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

objListOrder = GetOrderList()
                   .OrderBy(o => o.OrderId)
                   .ToList();
Daniel A. White
fuente
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;
Waqas Ahmed
fuente
3

Una versión mejorada de Roger.

El problema con GetDynamicSortProperty es que solo se obtienen los nombres de las propiedades, pero ¿qué sucede si en GridView usamos NavigationProperties? enviará una excepción, ya que encuentra nulo.

Ejemplo:

"Employee.Company.Name;" se bloqueará ... ya que solo permite que "Nombre" como parámetro obtenga su valor.

Aquí hay una versión mejorada que nos permite ordenar por propiedades de navegación.

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


    } 
Miguel Becerra
fuente
3

Puede hacer algo más genérico con respecto a la selección de propiedades y ser específico sobre el tipo de que está seleccionando, en su caso 'Pedido':

escribe tu función 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();
        } 

y luego úsalo así:

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

Puede ser aún más genérico y definir un tipo abierto para lo que desea pedir:

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

y úsalo de la misma manera:

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

Lo cual es una forma estúpida e innecesaria y compleja de hacer un estilo LINQ 'OrderBy', pero puede darle una idea de cómo se puede implementar de manera genérica

Danny Mor
fuente
3

Permítanme completar la respuesta de @LukeH con un código de muestra, ya que lo he probado y creo que puede ser útil para algunos:

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 ...
}
molbalga
fuente
3
var obj = db.Items.Where...

var orderBYItemId = obj.OrderByDescending(c => Convert.ToInt32(c.ID));
Jevgenij Kononov
fuente
1

Haz uso de LiNQ OrderBy

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

Basado en GenericTypeTea 's Comparer:
podemos obtener más flexibilidad agregando banderas de clasificación:

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

En este escenario, debe crear una instancia explícita como MyOrderingClass (en lugar de IComparer )
para establecer sus propiedades de clasificación:

MyOrderingClass comparer = new MyOrderingClass();  
comparer.DateDescending = ...;  
comparer.OrderIdDescending = ...;  
orderList.Sort(comparer);  
Jack Griffin
fuente
1

Ninguna de las respuestas anteriores fue lo suficientemente genérica para mí, así que hice 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();

Sin embargo, tenga cuidado con los conjuntos de datos masivos. Es un código fácil, pero podría meterte en problemas si la colección es enorme y el tipo de objeto de la colección tiene una gran cantidad de campos. El tiempo de ejecución es NxM donde:

N = # de elementos en la colección

M = # de propiedades dentro del objeto

itcropper
fuente
1

Cualquier persona que trabaje con tipos anulables Valuedebe usar CompareTo.

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

Judas
fuente
0

Desde el punto de vista del rendimiento, lo mejor es usar una lista ordenada para que los datos se clasifiquen a medida que se agregan al resultado. Otros enfoques necesitan al menos una iteración adicional en los datos y la mayoría crea una copia de los datos, por lo que no solo el rendimiento sino el uso de la memoria también se verán afectados. Puede que no sea un problema con un par de cientos de elementos, pero lo será con miles, especialmente en servicios en los que muchas solicitudes concurrentes pueden ordenar al mismo tiempo. Eche un vistazo a System.Collections.Generic namespace y elija una clase con clasificación en lugar de List.

Y evite implementaciones genéricas utilizando la reflexión cuando sea posible, esto también puede causar problemas de rendimiento.

usuario3285954
fuente
¿Cómo puede ser esto más eficiente? Insertar datos en una lista ordenada es O (n), por lo que agregar n elementos es O (n ^ 2). ¿Qué es lo que muchos elementos concurrentes están tratando de agregar? Hay situaciones en las que tiene ciclos de CPU adicionales para grabar mientras se agregan elementos, pero esta no es una buena solución general.
John La Rooy
0

Supongamos que tiene el siguiente código, en este código, tenemos una clase Passenger con un par de propiedades en las que queremos clasificar según.

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

    }
}

Para que pueda implementar su estructura de clasificación utilizando el delegado de composición.

Seyedraouf Modarresi
fuente