Ordinamento di un IList in C #


86

Quindi oggi mi sono imbattuto in un problema interessante. Abbiamo un servizio Web WCF che restituisce un IList. Non è stato un grosso problema finché non ho voluto risolverlo.

Si scopre che l'interfaccia IList non ha un metodo di ordinamento integrato.

Ho finito per usare il ArrayList.Adapter(list).Sort(new MyComparer())metodo per risolvere il problema ma mi è sembrato un po '"ghetto".

Ho giocato con la scrittura di un metodo di estensione, anche con l'ereditarietà da IList e l'implementazione del mio metodo Sort () così come il casting su un List, ma nessuno di questi sembrava eccessivamente elegante.

Quindi la mia domanda è: qualcuno ha una soluzione elegante per ordinare un IList


Perché dovresti restituire un ILista in primo luogo? Da un servizio WCF?
DaeMoohn

Risposte:


54

Che ne dici di usare LINQ To Objects per ordinare per te?

Supponiamo che tu abbia un IList<Car>e l'auto avesse una Engineproprietà, credo che potresti ordinare come segue:

from c in list
orderby c.Engine
select c;

Modifica: devi essere veloce per ottenere risposte qui. Poiché ho presentato una sintassi leggermente diversa per le altre risposte, lascerò la mia risposta, tuttavia, le altre risposte presentate sono ugualmente valide.


3
Creerà un nuovo enumerabile, che potrebbe non essere desiderabile in alcuni scenari. Non è possibile ordinare un IList <T> sul posto tramite l'interfaccia se non utilizzando il metodo ArrayList.Adapter a mia conoscenza.
Tanveer Badar

67

Puoi usare LINQ:

using System.Linq;

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

61

Questa domanda mi ha ispirato a scrivere un post sul blog: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

Penso che, idealmente, .NET Framework includerebbe un metodo di ordinamento statico che accetta un IList <T>, ma la cosa migliore da fare è creare il proprio metodo di estensione. Non è troppo difficile creare un paio di metodi che ti permetteranno di ordinare un IList <T> come faresti con un List <T>. Come bonus puoi sovraccaricare il metodo di estensione LINQ OrderBy utilizzando la stessa tecnica, in modo che, sia che tu stia usando List.Sort, IList.Sort o IEnumerable.OrderBy, puoi usare la stessa identica sintassi.

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 queste estensioni, ordina il tuo IList proprio come faresti con un elenco:

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

Ci sono maggiori informazioni nel post: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/


1
L'approccio giusto sarebbe stato davvero quello di offrire ISortableList<T>un'interfaccia (con metodi per ordinare una parte dell'elenco utilizzando un particolare comparatore), List<T>implementarla e avere un metodo statico che potrebbe ordinarne qualsiasi IList<T>controllando se è implementato ISortableList<T>e, in caso contrario, copiandolo in un array, ordinandolo, cancellando IList<T>e aggiungendo nuovamente gli elementi.
supercat

4
Risposta meravigliosa! Tuttavia, una parola di cautela: questo approccio presuppone che IList<T> listpossa essere eseguito il cast dell'interfaccia non generica IList. Se codifichi la tua classe implementando l' IList<T>interfaccia, assicurati di implementare anche l' IListinterfaccia non generica , altrimenti il ​​codice fallirà con un'eccezione di cast della classe.
sstan

1
@supercat: cosa potrebbe ISortableList<T>offrire che non sia già presente IList<T>? Oppure, chiesto in modo diverso, perché non è IList<T>possibile ordinare sul posto senza aggiungere nuovamente gli elementi con il metodo statico immaginato?
OR Mapper

@ORMapper: se un elenco utilizza un array come archivio di backup (comune, ma non obbligatorio), una routine di ordinamento che accede direttamente agli elementi dell'array può essere molto più veloce di una che deve passare attraverso l' IList<T>interfaccia per accedere a ogni elemento. La differenza di velocità è sufficientemente grande che in molti casi può essere più veloce copiare un elenco in un array, ordinare l'array e ricopiare l'elenco, piuttosto che provare a fare in modo che una routine di ordinamento elabori l'elenco in posizione.
supercat

1
La ComparisonComparerlezione non è necessaria. È invece possibile utilizzare il metodo statico standard Comparer<T>.Create(comparison).
linepogl

9

Dovrai fare qualcosa del genere, penso (convertirlo in un tipo più concreto).

Magari inseriscilo in un elenco di T piuttosto che in un elenco di array, in modo da ottenere l'indipendenza dai tipi e più opzioni su come implementare il comparatore.


4

La risposta accettata da @DavidMills è abbastanza buona, ma penso che possa essere migliorata. Per uno, non è necessario definire la ComparisonComparer<T>classe quando il framework include già un metodo statico Comparer<T>.Create(Comparison<T>). Questo metodo può essere utilizzato per creare un fileIComparison al volo.

Inoltre, lancia IList<T>a IListchi ha il potenziale per essere pericoloso. Nella maggior parte dei casi che ho visto, List<T>quale implementa IListviene utilizzato dietro le quinte per l'implementazioneIList<T> , ma questo non è garantito e può portare a codice fragile.

Infine, il List<T>.Sort()metodo sovraccarico ha 4 firme e solo 2 di esse sono implementate.

  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 classe seguente implementa tutte e 4 le List<T>.Sort()firme per l' IList<T>interfaccia:

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

Utilizzo:

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

L'idea qui è di sfruttare la funzionalità del sottostante List<T>per gestire l'ordinamento quando possibile. Di nuovo, la maggior parte delle IList<T>implementazioni che ho visto usano questo. Nel caso in cui la raccolta sottostante sia di un tipo diverso, eseguire il fallback per creare una nuova istanza di List<T>con elementi dall'elenco di input, utilizzarlo per eseguire l'ordinamento, quindi copiare i risultati nell'elenco di input. Funzionerà anche se l'elenco di input non implementa l' IListinterfaccia.


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

1

Ho trovato questo thread mentre cercavo una soluzione al problema esatto descritto nel post originale. Nessuna delle risposte ha soddisfatto completamente la mia situazione, tuttavia. La risposta di Brody è stata molto vicina. Ecco la mia situazione e la soluzione che ho trovato.

Ho due IList dello stesso tipo restituiti da NHibernate e sono emersi i due IList in uno, da qui la necessità dell'ordinamento.

Come ha detto Brody, ho implementato un ICompare sull'oggetto (ReportFormat) che è il tipo del mio IList:

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

Quindi converto l'IList unito in un array dello stesso tipo:

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

Quindi ordina l'array:

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

Poiché l'array unidimensionale implementa l'interfaccia System.Collections.Generic.IList<T>, l'array può essere utilizzato proprio come l'IList originale.


1

Utile per l'ordinamento della griglia, questo metodo ordina l'elenco in base ai nomi delle proprietà. Come segue l'esempio.

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

0

Ecco un esempio che utilizza la digitazione più forte. Non sono sicuro che sia necessariamente il modo migliore però.

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 funzione Cast è solo una reimplementazione del metodo di estensione fornito con 3.5 scritto come un normale metodo statico. Purtroppo è piuttosto brutto e prolisso.


0

In VS2008, quando clicco sul riferimento del servizio e seleziono "Configura riferimento servizio", è disponibile un'opzione per scegliere come il client deserializza gli elenchi restituiti dal servizio.

In particolare, posso scegliere tra System.Array, System.Collections.ArrayList e System.Collections.Generic.List


0
using System.Linq;

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

È carino! Ghetto.


0

Ho trovato un buon post su questo e ho pensato di condividerlo. Controllalo qui

Fondamentalmente.

È possibile creare la seguente classe e le classi 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);
}
}

Quindi se hai un IList, puoi ordinarlo in questo modo.

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

Ma controlla questo sito per ulteriori informazioni ... Dai un'occhiata QUI


0

È una soluzione valida?

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

Il risultato è stato: IList B A C

Elenco A B C

ILista di nuovo A B C


1
Valido se è davvero un List <T>. In alcuni casi, sono presenti altri tipi che implementano IList <T> (ad esempio, un array semplice) in cui il downcast non funzionerebbe. Peccato che il metodo Sort () non sia un metodo di estensione a IList <T>.
Cygon

0

Questo sembra MOLTO PIÙ SEMPLICE se me lo chiedi. Questo funziona PERFETTAMENTE per me.

Puoi usare Cast () per cambiarlo in IList, quindi usare OrderBy ():

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

DOVE T è il tipo ad es. Model.Employee o Plugin.ContactService.Shared.Contact

Quindi puoi usare un ciclo for e il suo FATTO.

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

    foreach (var item in ordered)
    {
       ContactItems.Add(item);
    }

-1

Converti la tua IListin List<T>o qualche altra raccolta generica e quindi puoi facilmente interrogarla / ordinarla usando lo System.Linqspazio dei nomi (fornirà un sacco di metodi di estensione)


9
IList<T>implementa IEnumerable<T>e quindi non ha bisogno di essere convertito per utilizzare le operazioni di Linq.
Steve Guidi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.