Classificando um IList em C #

86

Então me deparei com um problema interessante hoje. Temos um serviço da web WCF que retorna um IList. Não era realmente grande coisa até que eu quisesse resolver isso.

Acontece que a interface IList não tem um método de classificação embutido.

Acabei usando o ArrayList.Adapter(list).Sort(new MyComparer())método para resolver o problema, mas parecia um pouco "gueto" para mim.

Eu brinquei em escrever um método de extensão, também em herdar de IList e implementar meu próprio método Sort (), bem como lançar para um List, mas nenhum deles parecia excessivamente elegante.

Então, minha pergunta é, alguém tem uma solução elegante para classificar um ILista

lomaxx
fonte
Por que você retornaria um IList em primeiro lugar? De um serviço WCF?
DaeMoohn

Respostas:

54

Que tal usar LINQ To Objects para classificar para você?

Digamos que você tenha um IList<Car>, e o carro tenha uma Enginepropriedade, acredito que você possa classificar da seguinte forma:

from c in list
orderby c.Engine
select c;

Edit: Você precisa ser rápido para obter respostas aqui. Como apresentei uma sintaxe um pouco diferente para as outras respostas, deixarei minha resposta - entretanto, as outras respostas apresentadas são igualmente válidas.

Brad Leach
fonte
2
Isso criará um novo enumerável, o que pode não ser desejável em alguns cenários. Você não pode classificar um IList <T> no local por meio da interface, exceto usando o método ArrayList.Adapter que eu saiba.
Tanveer Badar
66

Você pode usar o 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
fonte
61

Esta pergunta me inspirou a escrever uma postagem no blog: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

Acho que, idealmente, o .NET Framework incluiria um método de classificação estático que aceita um IList <T>, mas a próxima melhor coisa é criar seu próprio método de extensão. Não é muito difícil criar alguns métodos que permitirão que você classifique um IList <T> como faria com um List <T>. Como bônus, você pode sobrecarregar o método de extensão LINQ OrderBy usando a mesma técnica, de forma que, se estiver usando List.Sort, IList.Sort ou IEnumerable.OrderBy, você poderá usar exatamente a mesma sintaxe.

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

Com essas extensões, classifique seu IList como se fosse uma lista:

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

Há mais informações na postagem: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

David Mills
fonte
1
A abordagem certa realmente teria sido oferecer um ISortableList<T> interface (com métodos para classificar uma parte da lista usando algum comparador específico), List<T>implementá-la e ter um método estático que pudesse classificar qualquer uma IList<T>, verificando se ela foi implementada ISortableList<T>e, se não, copiando-o para uma matriz, classificando-a, limpando IList<T>e readicionando os itens.
supercat
4
Resposta maravilhosa! No entanto, uma palavra de cautela: essa abordagem pressupõe que o IList<T> listpode ser convertido para a IListinterface não genérica . Se você codificar sua própria classe implementando a IList<T>interface, certifique-se de implementar também o método não genéricoIList interface , ou o código falhará com uma exceção de conversão de classe.
sstan
1
@supercat: O que poderia ISortableList<T>oferecer que ainda não foi lançado IList<T>? Ou, de outra forma, por que não poderia IList<T>ser classificado no local sem readicionar os itens pelo seu método estático imaginado?
OR Mapper
@ORMapper: Se uma lista usa uma matriz como armazenamento de apoio (comum, mas não obrigatório), uma rotina de classificação que acessa os elementos da matriz diretamente pode ser muito mais rápida do que aquela que precisa passar pelo IList<T> interface para acessar cada elemento. A diferença de velocidade é grande o suficiente para que, em muitos casos, seja mais rápido copiar uma lista para um array, classificar o array e copiar a lista de volta do que tentar fazer com que uma rotina de classificação processe a lista no local.
supercat de
1
A ComparisonCompareraula não é necessária. Você pode usar o método estático padrão Comparer<T>.Create(comparison).
linepogl
9

Você vai ter que fazer algo assim, eu acho (convertê-lo em um tipo mais concreto).

Talvez inclua em uma List of T em vez de ArrayList, para obter segurança de tipo e mais opções de como implementar o comparador.

Leon Bambrick
fonte
4

A resposta aceita por @DavidMills é muito boa, mas acho que pode ser melhorada. Por um lado, não há necessidade de definir a ComparisonComparer<T>classe quando o framework já inclui um método estático Comparer<T>.Create(Comparison<T>). Este método pode ser usado para criar umIComparison em tempo real.

Além disso, ele transmite IList<T>para o IListqual tem potencial para ser perigoso. Na maioria dos casos que vi, o List<T>que implementa IListé usado nos bastidores para implementarIList<T> , mas isso não é garantido e pode levar a um código frágil.

Por último, o List<T>.Sort()método sobrecarregado possui 4 assinaturas e apenas 2 delas estão 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>)

A classe abaixo implementa todas as 4 List<T>.Sort()assinaturas para a IList<T>interface:

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

A ideia aqui é aproveitar a funcionalidade do subjacente List<T>para lidar com a classificação sempre que possível. Novamente, a maioria das IList<T>implementações que vi usa isso. No caso em que a coleção subjacente é de um tipo diferente, volte para a criação de uma nova instância de List<T>com elementos da lista de entrada, use-a para fazer a classificação e copie os resultados de volta para a lista de entrada. Isso funcionará mesmo se a lista de entrada não implementar a IListinterface.

dana
fonte
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
fonte
1

Encontrei este tópico enquanto procurava uma solução para o problema exato descrito no post original. Nenhuma das respostas atendeu inteiramente a minha situação, no entanto. A resposta de Brody foi bem próxima. Aqui está minha situação e a solução que encontrei para ela.

Eu tenho dois IList do mesmo tipo retornados por NHibernate e emergi os dois IList em um, daí a necessidade de classificação.

Como o Brody disse, implementei um ICompare no objeto (ReportFormat) que é o tipo do meu IList:

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

Em seguida, converto o IList mesclado em uma matriz do mesmo tipo:

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

Em seguida, classifique a matriz:

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

Visto que o array unidimensional implementa a interface System.Collections.Generic.IList<T>, o array pode ser usado como o IList original.

John
fonte
1

Útil para classificação em grade, este método classifica a lista com base nos nomes das propriedades. Siga o exemplo.

    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
fonte
0

Aqui está um exemplo usando a digitação mais forte. Não tenho certeza se é necessariamente a melhor maneira.

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

A função Cast é apenas uma reimplementação do método de extensão que vem com o 3.5 escrito como um método estático normal. É muito feio e prolixo, infelizmente.

ICR
fonte
0

No VS2008, quando clico na referência do serviço e seleciono "Configurar Referência do Serviço", há uma opção de escolher como o cliente desserializa as listas retornadas do serviço.

Notavelmente, posso escolher entre System.Array, System.Collections.ArrayList e System.Collections.Generic.List

Amy B
fonte
0
using System.Linq;

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

Isso é lindo! Gueto.


fonte
0

Encontrei um bom post sobre isso e pensei em compartilhar. Confira aqui

Basicamente.

Você pode criar a seguinte classe e classes 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);
}
}

Então, se você tiver um IList, você pode classificá-lo assim.

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

Mas verifique este site para mais informações ... Confira AQUI

Mizipzor
fonte
0

Esta é uma solução 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();

O resultado foi: IList B A C

Lista A B C

ILista novamente A B C

Yoav
fonte
1
Válido se for realmente um List <T>. Em alguns casos, você tem outros tipos implementando IList <T> (por exemplo, um array simples) onde o downcast não funcionaria. Uma pena que o método Sort () não é um método de extensão para IList <T>.
Cygon
0

Isso parece MUITO MAIS SIMPLES se você me perguntar. Isso funciona perfeitamente para mim.

Você pode usar Cast () para alterá-lo para IList e usar OrderBy ():

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

ONDE T é o tipo, por exemplo. Model.Employee ou Plugin.ContactService.Shared.Contact

Então você pode usar um loop for e seu DONE.

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

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

Converta o seu IListem List<T>ou alguma outra coleção genérica e então você pode facilmente consultar / classificar usando o System.Linqnamespace (ele fornecerá vários métodos de extensão)

lubos hasko
fonte
9
IList<T>implementa IEnumerable<T>e, portanto, não precisa ser convertido para usar as operações do Linq.
Steve Guidi