Ordenar un IList en C #

86

Así que hoy me encontré con un problema interesante. Tenemos un servicio web WCF que devuelve un IList. No es gran cosa hasta que quise arreglarlo.

Resulta que la interfaz IList no tiene un método de ordenación integrado.

Terminé usando el ArrayList.Adapter(list).Sort(new MyComparer()) método para resolver el problema, pero me pareció un poco "gueto".

Jugué con escribir un método de extensión, también con heredar de IList e implementar mi propio método Sort (), así como convertirlo en una Lista, pero ninguno de estos parecía demasiado elegante.

Entonces mi pregunta es, ¿alguien tiene una solución elegante para ordenar un IList

lomaxx
fuente
¿Por qué devolverías un IList en primer lugar? ¿De un servicio WCF?
DaeMoohn

Respuestas:

54

¿Qué hay de usar LINQ To Objects para ordenar por usted?

Supongamos que tiene un IList<Car>, y el automóvil tiene una Enginepropiedad, creo que podría ordenar de la siguiente manera:

from c in list
orderby c.Engine
select c;

Editar: debe ser rápido para obtener respuestas aquí. Como presenté una sintaxis ligeramente diferente a las otras respuestas, dejaré mi respuesta; sin embargo, las otras respuestas presentadas son igualmente válidas.

Brad Leach
fuente
3
Creará un nuevo enumerable, que puede no ser deseable en algunos escenarios. No puede ordenar un IList <T> en el lugar a través de la interfaz, excepto mediante el método ArrayList.Adapter que yo sepa.
Tanveer Badar
67

Puede utilizar LINQ:

using System.Linq;

IList<Foo> list = new List<Foo>();
IEnumerable<Foo> sortedEnum = list.OrderBy(f=>f.Bar);
IList<Foo> sortedList = sortedEnum.ToList();
Mark Cidade
fuente
61

Esta pregunta me inspiró a escribir una publicación de blog: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

Creo que, idealmente, .NET Framework incluiría un método de clasificación estático que acepta un IList <T>, pero lo mejor es crear su propio método de extensión. No es demasiado difícil crear un par de métodos que le permitan ordenar un IList <T> como lo haría con un List <T>. Como beneficio adicional, puede sobrecargar el método de extensión LINQ OrderBy utilizando la misma técnica, de modo que si está usando List.Sort, IList.Sort o IEnumerable.OrderBy, puede usar exactamente la misma sintaxis.

public static class SortExtensions
{
    //  Sorts an IList<T> in place.
    public static void Sort<T>(this IList<T> list, Comparison<T> comparison)
    {
        ArrayList.Adapter((IList)list).Sort(new ComparisonComparer<T>(comparison));
    }

    // Sorts in IList<T> in place, when T is IComparable<T>
    public static void Sort<T>(this IList<T> list) where T: IComparable<T>
    {
        Comparison<T> comparison = (l, r) => l.CompareTo(r);
        Sort(list, comparison);

    }

    // Convenience method on IEnumerable<T> to allow passing of a
    // Comparison<T> delegate to the OrderBy method.
    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> list, Comparison<T> comparison)
    {
        return list.OrderBy(t => t, new ComparisonComparer<T>(comparison));
    }
}

// Wraps a generic Comparison<T> delegate in an IComparer to make it easy
// to use a lambda expression for methods that take an IComparer or IComparer<T>
public class ComparisonComparer<T> : IComparer<T>, IComparer
{
    private readonly Comparison<T> _comparison;

    public ComparisonComparer(Comparison<T> comparison)
    {
        _comparison = comparison;
    }

    public int Compare(T x, T y)
    {
        return _comparison(x, y);
    }

    public int Compare(object o1, object o2)
    {
        return _comparison((T)o1, (T)o2);
    }
}

Con estas extensiones, ordena tu IList como lo harías con una List:

IList<string> iList = new []
{
    "Carlton", "Alison", "Bob", "Eric", "David"
};

// Use the custom extensions:

// Sort in-place, by string length
iList.Sort((s1, s2) => s1.Length.CompareTo(s2.Length));

// Or use OrderBy()
IEnumerable<string> ordered = iList.OrderBy((s1, s2) => s1.Length.CompareTo(s2.Length));

Hay más información en la publicación: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

David Mills
fuente
1
El enfoque correcto realmente habría sido ofrecer una ISortableList<T>interfaz (con métodos para ordenar una parte de la lista usando algún comparador en particular), List<T>implementarla y tener un método estático que podría ordenar cualquiera IList<T>verificando si se implementó ISortableList<T>y, si no, copiarlo en una matriz, ordenarlo, borrarlo IList<T>y volver a agregar los elementos.
supercat
4
¡Maravillosa respuesta! Sin embargo, una advertencia: este enfoque asume que IList<T> listse puede convertir a la IListinterfaz no genérica . Si codifica su propia clase implementando la IList<T>interfaz, asegúrese de implementar también la IListinterfaz no genérica , o el código fallará con una excepción de conversión de clase.
2015
1
@supercat: ¿Qué podría ISortableList<T>ofrecer que no esté ya incluido IList<T>? O, preguntado de otra manera, ¿por qué no se IList<T>puede ordenar en el lugar sin volver a agregar los elementos con su método estático imaginado?
OR Mapper
@ORMapper: Si una lista usa una matriz como almacén de respaldo (común, pero no obligatorio), una rutina de clasificación que accede a los elementos de la matriz directamente puede ser mucho más rápida que una que tiene que pasar por la IList<T>interfaz para acceder a cada elemento. La diferencia de velocidad es lo suficientemente grande como para que, en muchos casos, sea más rápido copiar una lista en una matriz, ordenar la matriz y copiar la lista de nuevo, que intentar tener una rutina de clasificación que procese la lista en su lugar.
supercat
1
La ComparisonComparerclase no es necesaria. En su lugar, puede utilizar el método estático estándar Comparer<T>.Create(comparison).
linepogl
9

Creo que vas a tener que hacer algo así (convertirlo en un tipo más concreto).

Tal vez lo incluya en una Lista de T en lugar de ArrayList, de modo que obtenga seguridad de tipos y más opciones sobre cómo implementar el comparador.

Leon Bambrick
fuente
4

La respuesta aceptada por @DavidMills es bastante buena, pero creo que se puede mejorar. Por un lado, no es necesario definir la ComparisonComparer<T>clase cuando el marco ya incluye un método estático Comparer<T>.Create(Comparison<T>). Este método se puede utilizar para crear un archivo IComparisonsobre la marcha.

Además, lanza IList<T>a lo IListque tiene el potencial de ser peligroso. En la mayoría de los casos que he visto, List<T>qué implementos IListse utilizan entre bastidores para implementar IList<T>, pero esto no está garantizado y puede generar un código frágil.

Por último, el List<T>.Sort()método sobrecargado tiene 4 firmas y solo 2 de ellas están implementadas.

  1. List<T>.Sort()
  2. List<T>.Sort(Comparison<T>)
  3. List<T>.Sort(IComparer<T>)
  4. List<T>.Sort(Int32, Int32, IComparer<T>)

La siguiente clase implementa las 4 List<T>.Sort()firmas para la IList<T>interfaz:

using System;
using System.Collections.Generic;

public static class IListExtensions
{
    public static void Sort<T>(this IList<T> list)
    {
        if (list is List<T>)
        {
            ((List<T>)list).Sort();
        }
        else
        {
            List<T> copy = new List<T>(list);
            copy.Sort();
            Copy(copy, 0, list, 0, list.Count);
        }
    }

    public static void Sort<T>(this IList<T> list, Comparison<T> comparison)
    {
        if (list is List<T>)
        {
            ((List<T>)list).Sort(comparison);
        }
        else
        {
            List<T> copy = new List<T>(list);
            copy.Sort(comparison);
            Copy(copy, 0, list, 0, list.Count);
        }
    }

    public static void Sort<T>(this IList<T> list, IComparer<T> comparer)
    {
        if (list is List<T>)
        {
            ((List<T>)list).Sort(comparer);
        }
        else
        {
            List<T> copy = new List<T>(list);
            copy.Sort(comparer);
            Copy(copy, 0, list, 0, list.Count);
        }
    }

    public static void Sort<T>(this IList<T> list, int index, int count,
        IComparer<T> comparer)
    {
        if (list is List<T>)
        {
            ((List<T>)list).Sort(index, count, comparer);
        }
        else
        {
            List<T> range = new List<T>(count);
            for (int i = 0; i < count; i++)
            {
                range.Add(list[index + i]);
            }
            range.Sort(comparer);
            Copy(range, 0, list, index, count);
        }
    }

    private static void Copy<T>(IList<T> sourceList, int sourceIndex,
        IList<T> destinationList, int destinationIndex, int count)
    {
        for (int i = 0; i < count; i++)
        {
            destinationList[destinationIndex + i] = sourceList[sourceIndex + i];
        }
    }
}

Uso:

class Foo
{
    public int Bar;

    public Foo(int bar) { this.Bar = bar; }
}

void TestSort()
{
    IList<int> ints = new List<int>() { 1, 4, 5, 3, 2 };
    IList<Foo> foos = new List<Foo>()
    {
        new Foo(1),
        new Foo(4),
        new Foo(5),
        new Foo(3),
        new Foo(2),
    };

    ints.Sort();
    foos.Sort((x, y) => Comparer<int>.Default.Compare(x.Bar, y.Bar));
}

La idea aquí es aprovechar la funcionalidad del subyacente List<T>para manejar la clasificación siempre que sea posible. Nuevamente, la mayoría de las IList<T>implementaciones que he visto usan esto. En el caso de que la colección subyacente sea de un tipo diferente, recurra a la creación de una nueva instancia de List<T>con elementos de la lista de entrada, úsela para ordenar y luego copie los resultados a la lista de entrada. Esto funcionará incluso si la lista de entrada no implementa la IListinterfaz.

dana
fuente
2
try this  **USE ORDER BY** :

   public class Employee
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }

 private static IList<Employee> GetItems()
        {
            List<Employee> lst = new List<Employee>();

            lst.Add(new Employee { Id = "1", Name = "Emp1" });
            lst.Add(new Employee { Id = "2", Name = "Emp2" });
            lst.Add(new Employee { Id = "7", Name = "Emp7" });
            lst.Add(new Employee { Id = "4", Name = "Emp4" });
            lst.Add(new Employee { Id = "5", Name = "Emp5" });
            lst.Add(new Employee { Id = "6", Name = "Emp6" });
            lst.Add(new Employee { Id = "3", Name = "Emp3" });

            return lst;
        }

**var lst = GetItems().AsEnumerable();

            var orderedLst = lst.OrderBy(t => t.Id).ToList();

            orderedLst.ForEach(emp => Console.WriteLine("Id - {0} Name -{1}", emp.Id, emp.Name));**
Dhanasekar
fuente
1

Encontré este hilo mientras buscaba una solución al problema exacto descrito en la publicación original. Sin embargo, ninguna de las respuestas satisfizo mi situación por completo. La respuesta de Brody estuvo bastante cerca. Aquí está mi situación y la solución que encontré.

Tengo dos ILists del mismo tipo devueltos por NHibernate y los dos IList se han convertido en uno, de ahí la necesidad de ordenar.

Como dijo Brody, implementé un ICompare en el objeto (ReportFormat) que es el tipo de mi IList:

 public class FormatCcdeSorter:IComparer<ReportFormat>
    {
       public int Compare(ReportFormat x, ReportFormat y)
        {
           return x.FormatCode.CompareTo(y.FormatCode);
        }
    }

Luego convierto el IList combinado en una matriz del mismo tipo:

ReportFormat[] myReports = new ReportFormat[reports.Count]; //reports is the merged IList

Luego ordena la matriz:

Array.Sort(myReports, new FormatCodeSorter());//sorting using custom comparer

Dado que la matriz unidimensional implementa la interfaz System.Collections.Generic.IList<T>, la matriz se puede utilizar como la IList original.

Juan
fuente
1

Útil para ordenar la cuadrícula, este método clasifica la lista según los nombres de propiedad. Como sigue el ejemplo.

    List<MeuTeste> temp = new List<MeuTeste>();

    temp.Add(new MeuTeste(2, "ramster", DateTime.Now));
    temp.Add(new MeuTeste(1, "ball", DateTime.Now));
    temp.Add(new MeuTeste(8, "gimm", DateTime.Now));
    temp.Add(new MeuTeste(3, "dies", DateTime.Now));
    temp.Add(new MeuTeste(9, "random", DateTime.Now));
    temp.Add(new MeuTeste(5, "call", DateTime.Now));
    temp.Add(new MeuTeste(6, "simple", DateTime.Now));
    temp.Add(new MeuTeste(7, "silver", DateTime.Now));
    temp.Add(new MeuTeste(4, "inn", DateTime.Now));

    SortList(ref temp, SortDirection.Ascending, "MyProperty");

    private void SortList<T>(
    ref List<T> lista
    , SortDirection sort
    , string propertyToOrder)
    {
        if (!string.IsNullOrEmpty(propertyToOrder)
        && lista != null
        && lista.Count > 0)
        {
            Type t = lista[0].GetType();

            if (sort == SortDirection.Ascending)
            {
                lista = lista.OrderBy(
                    a => t.InvokeMember(
                        propertyToOrder
                        , System.Reflection.BindingFlags.GetProperty
                        , null
                        , a
                        , null
                    )
                ).ToList();
            }
            else
            {
                lista = lista.OrderByDescending(
                    a => t.InvokeMember(
                        propertyToOrder
                        , System.Reflection.BindingFlags.GetProperty
                        , null
                        , a
                        , null
                    )
                ).ToList();
            }
        }
    }
Bruno
fuente
0

A continuación, se muestra un ejemplo con el tipo de escritura más fuerte. Sin embargo, no estoy seguro de si es necesariamente la mejor manera.

static void Main(string[] args)
{
    IList list = new List<int>() { 1, 3, 2, 5, 4, 6, 9, 8, 7 };
    List<int> stronglyTypedList = new List<int>(Cast<int>(list));
    stronglyTypedList.Sort();
}

private static IEnumerable<T> Cast<T>(IEnumerable list)
{
    foreach (T item in list)
    {
        yield return item;
    }
}

La función Cast es solo una reimplementación del método de extensión que viene con 3.5 escrito como un método estático normal. Desafortunadamente, es bastante feo y detallado.

ICR
fuente
0

En VS2008, cuando hago clic en la referencia de servicio y selecciono "Configurar referencia de servicio", hay una opción para elegir cómo el cliente deserializa las listas devueltas del servicio.

En particular, puedo elegir entre System.Array, System.Collections.ArrayList y System.Collections.Generic.List

Amy B
fuente
0
using System.Linq;

var yourList = SomeDAO.GetRandomThings();
yourList.ToList().Sort( (thing, randomThing) => thing.CompareThisProperty.CompareTo( randomThing.CompareThisProperty ) );

¡Eso es bonito! Ghetto.


fuente
0

Encontré una buena publicación sobre esto y pensé en compartir. Compruébalo AQUÍ

Básicamente.

Puede crear la siguiente clase y clases IComparer

public class Widget {
    public string Name = string.Empty;
    public int Size = 0;

    public Widget(string name, int size) {
    this.Name = name;
    this.Size = size;
}
}

public class WidgetNameSorter : IComparer<Widget> {
    public int Compare(Widget x, Widget y) {
        return x.Name.CompareTo(y.Name);
}
}

public class WidgetSizeSorter : IComparer<Widget> {
    public int Compare(Widget x, Widget y) {
    return x.Size.CompareTo(y.Size);
}
}

Entonces, si tiene un IList, puede ordenarlo así.

List<Widget> widgets = new List<Widget>();
widgets.Add(new Widget("Zeta", 6));
widgets.Add(new Widget("Beta", 3));
widgets.Add(new Widget("Alpha", 9));

widgets.Sort(new WidgetNameSorter());
widgets.Sort(new WidgetSizeSorter());

Pero consulte este sitio para obtener más información ... Visítelo AQUÍ

Mizipzor
fuente
0

¿Es esta una solución válida?

        IList<string> ilist = new List<string>();
        ilist.Add("B");
        ilist.Add("A");
        ilist.Add("C");

        Console.WriteLine("IList");
        foreach (string val in ilist)
            Console.WriteLine(val);
        Console.WriteLine();

        List<string> list = (List<string>)ilist;
        list.Sort();
        Console.WriteLine("List");
        foreach (string val in list)
            Console.WriteLine(val);
        Console.WriteLine();

        list = null;

        Console.WriteLine("IList again");
        foreach (string val in ilist)
            Console.WriteLine(val);
        Console.WriteLine();

El resultado fue: IList B A C

Lista A B C

ILista de nuevo A B C

Yoav
fuente
1
Válido si es realmente una List <T>. En algunos casos, tiene otros tipos que implementan IList <T> (por ejemplo, una matriz simple) donde el abatido no funcionaría. Lástima que el método Sort () no sea un método de extensión para IList <T>.
Cygon
0

Esto parece MUCHO MÁS SIMPLE si me preguntas. Esto funciona perfectamente para mi.

Puede usar Cast () para cambiarlo a IList y luego usar OrderBy ():

    var ordered = theIList.Cast<T>().OrderBy(e => e);

DONDE T es el tipo, por ejemplo. Model.Employee o Plugin.ContactService.Shared.Contact

Entonces puedes usar un bucle for y está HECHO.

  ObservableCollection<Plugin.ContactService.Shared.Contact> ContactItems= new ObservableCollection<Contact>();

    foreach (var item in ordered)
    {
       ContactItems.Add(item);
    }
Momodu Deen Swarray
fuente
-1

Convierta su colección IListen List<T>o en alguna otra colección genérica y luego podrá consultarla / ordenarla fácilmente usando el System.Linqespacio de nombres (proporcionará un montón de métodos de extensión)

lubos hasko
fuente
9
IList<T>implementa IEnumerable<T>y, por lo tanto, no es necesario convertirlo para usar operaciones de Linq.
Steve Guidi