Sortowanie IList w C #

86

Więc dzisiaj natrafiłem na ciekawy problem. Mamy usługę sieci Web WCF, która zwraca IList. Nie było to nic wielkiego, dopóki nie chciałem tego uporządkować.

Okazuje się, że interfejs IList nie ma wbudowanej metody sortowania.

Skończyło się na tym, że użyłem tej ArrayList.Adapter(list).Sort(new MyComparer())metody do rozwiązania problemu, ale wydawało mi się to trochę „getto”.

Bawiłem się pisaniem metody rozszerzającej, a także dziedziczeniem po IList i implementowaniem własnej metody Sort (), a także rzutowaniem na Listę, ale żadna z nich nie wydawała się zbyt elegancka.

Więc moje pytanie brzmi, czy ktoś ma eleganckie rozwiązanie do sortowania IList

lomaxx
źródło
Dlaczego w ogóle miałbyś zwrócić IList? Z usługi WCF?
DaeMoohn

Odpowiedzi:

55

A co powiesz na używanie LINQ To Objects do sortowania?

Powiedzmy, że masz IList<Car>, a samochód miał Enginewłaściwość, myślę, że możesz sortować w następujący sposób:

from c in list
orderby c.Engine
select c;

Edycja: musisz być szybki, aby uzyskać odpowiedzi tutaj. Ponieważ przedstawiłem nieco inną składnię niż pozostałe odpowiedzi, zostawię swoją odpowiedź - jednak pozostałe przedstawione odpowiedzi są równie ważne.

Brad Leach
źródło
3
Stworzy to nowe wyliczalne, co może nie być pożądane w niektórych scenariuszach. Nie można sortować w miejscu IList <T> w interfejsie, chyba że przy użyciu metody ArrayList.Adapter.
Tanveer Badar
67

Możesz użyć 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
źródło
61

To pytanie zainspirowało mnie do napisania wpisu na blogu: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

Myślę, że idealnie byłoby, gdyby .NET Framework zawierał statyczną metodę sortowania, która akceptuje IList <T>, ale następną najlepszą rzeczą jest utworzenie własnej metody rozszerzającej. Nie jest trudno stworzyć kilka metod, które pozwolą ci posortować IList <T> tak jak List <T>. Jako bonus możesz przeciążać metodę rozszerzenia LINQ OrderBy przy użyciu tej samej techniki, dzięki czemu niezależnie od tego, czy używasz List.Sort, IList.Sort czy IEnumerable.OrderBy, możesz użyć dokładnie tej samej składni.

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

Dzięki tym rozszerzeniom posortuj swój IList tak samo jak 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));

Więcej informacji znajduje się w poście: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

David Mills
źródło
1
Naprawdę właściwym podejściem byłoby zaoferowanie ISortableList<T>interfejsu (z metodami do sortowania części listy przy użyciu określonej funkcji porównującej), List<T>zaimplementowanie go i posiadanie metody statycznej, która mogłaby sortować dowolne IList<T>, sprawdzając, czy jest zaimplementowana, ISortableList<T>a jeśli nie, skopiowanie go do tablicy, posortowanie tego, wyczyszczenie IList<T>i ponowne dodanie elementów.
supercat
4
Wspaniała odpowiedź! Jednak uwaga: to podejście zakłada, że IList<T> listmożna rzutować na IListinterfejs inny niż ogólny . Jeśli kodujesz własną klasę implementującą IList<T>interfejs, upewnij się, że implementujesz również IListinterfejs inny niż ogólny , w przeciwnym razie kod zakończy się niepowodzeniem z wyjątkiem rzutowania klasy.
sstan
1
@supercat: Co może ISortableList<T>zaoferować, czego jeszcze nie ma IList<T>? Albo inaczej, dlaczego nie można IList<T>było posortować na miejscu bez ponownego dodawania elementów za pomocą wyimaginowanej metody statycznej?
LUB Mapper
@ORMapper: Jeśli lista używa tablicy jako magazynu zapasowego (powszechne, ale nie wymagane), procedura sortowania, która uzyskuje bezpośredni dostęp do elementów tablicy, może być znacznie szybsza niż ta, która musi przejść przez IList<T>interfejs, aby uzyskać dostęp do każdego elementu. Różnica w szybkości jest na tyle duża, że ​​w wielu przypadkach szybsze może być skopiowanie listy do tablicy, posortowanie tablicy i skopiowanie listy z powrotem, niż próba wykonania procedury sortowania w miejscu.
supercat
1
ComparisonComparerKlasa nie jest konieczne. Zamiast tego można użyć standardowej metody statycznej Comparer<T>.Create(comparison).
linepogl
9

Myślę, że będziesz musiał zrobić coś takiego (zamień to na bardziej konkretny typ).

Może przenieś to do listy T zamiast ArrayList, aby uzyskać bezpieczeństwo typów i więcej opcji implementacji elementu porównującego.

Leon Bambrick
źródło
4

Przyjęta odpowiedź @DavidMills jest całkiem dobra, ale myślę, że można ją poprawić. Po pierwsze, nie ma potrzeby definiowania ComparisonComparer<T>klasy, jeśli framework zawiera już metodę statyczną Comparer<T>.Create(Comparison<T>). Ta metoda może być używana do tworzenia IComparisonw locie.

Ponadto rzuca, IList<T>do IListktórego może być niebezpieczny. W większości przypadków, które widziałem, List<T>które implementacje IListsą używane za kulisami do implementacji IList<T>, ale nie jest to gwarantowane i może prowadzić do kruchego kodu.

Wreszcie przeciążona List<T>.Sort()metoda ma 4 sygnatury i tylko 2 z nich są zaimplementowane.

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

Poniższa klasa implementuje wszystkie 4 List<T>.Sort()sygnatury IList<T>interfejsu:

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

Stosowanie:

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

Pomysł polega na wykorzystaniu funkcjonalności elementu bazowego List<T>do obsługi sortowania, gdy tylko jest to możliwe. Ponownie, większość IList<T>implementacji, które widziałem, używa tego. W przypadku, gdy źródłowa kolekcja jest innego typu, wróć do tworzenia nowego wystąpienia List<T>z elementami z listy wejściowej, użyj jej do sortowania, a następnie skopiuj wyniki z powrotem do listy wejściowej. To zadziała, nawet jeśli lista wejściowa nie implementuje IListinterfejsu.

dana
źródło
3
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
źródło
A co jeśli chcesz, aby był to DESC zamiast ASC?
sam bajt
1

Znalazłem ten wątek, gdy szukałem rozwiązania dokładnego problemu opisanego w oryginalnym poście. Żadna z odpowiedzi nie odpowiadała jednak całkowicie mojej sytuacji. Odpowiedź Brody'ego była dość bliska. Oto moja sytuacja i rozwiązanie, które znalazłem.

Mam dwóch IListów tego samego typu zwróconych przez NHibernate i połączyłem te dwa IList w jeden, stąd potrzeba sortowania.

Jak powiedział Brody, zaimplementowałem ICompare na obiekcie (ReportFormat), który jest typem mojego IList:

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

Następnie konwertuję scalony IList na tablicę tego samego typu:

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

Następnie posortuj tablicę:

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

Ponieważ jednowymiarowa tablica implementuje interfejs System.Collections.Generic.IList<T>, tablica może być używana tak jak oryginalna IList.

Jan
źródło
1

Przydatna do sortowania według siatki. Ta metoda sortuje listę na podstawie nazw właściwości. Postępuj zgodnie z przykładem.

    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
źródło
0

Oto przykład użycia silniejszego pisania. Nie jestem jednak pewien, czy to koniecznie najlepszy sposób.

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

Funkcja Cast to po prostu reimplementacja metody rozszerzającej, która jest dostarczana z 3.5 zapisanym jako normalna metoda statyczna. Jest to niestety dość brzydkie i rozwlekłe.

ICR
źródło
0

W VS2008, kiedy klikam odwołanie do usługi i wybieram opcję „Konfiguruj odwołanie do usługi”, istnieje opcja wyboru sposobu, w jaki klient deserializuje listy zwracane przez usługę.

W szczególności mogę wybierać między System.Array, System.Collections.ArrayList i System.Collections.Generic.List

Amy B.
źródło
0
using System.Linq;

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

To ładne! Getto.


źródło
0

Znalazłem dobry post na ten temat i pomyślałem, że się nim podzielę. Sprawdź to TUTAJ

Gruntownie.

Możesz utworzyć następujące klasy i klasy 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);
}
}

Jeśli masz IList, możesz to posortować w ten sposób.

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

Ale sprawdź tę stronę, aby uzyskać więcej informacji ... Sprawdź to TUTAJ

Mizipzor
źródło
0

Czy to prawidłowe rozwiązanie?

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

Wynik był następujący: IList B A C

Lista A B C

IList ponownie A B C

Yoav
źródło
1
Prawidłowe, jeśli to naprawdę List <T>. W niektórych przypadkach masz inne typy implementujące IList <T> (na przykład zwykłą tablicę), w której downcast nie zadziała. Szkoda, że ​​metoda Sort () nie jest metodą rozszerzającą do IList <T>.
Cygon
0

To wygląda O WIELE BARDZIEJ PROSTEJ, jeśli o mnie chodzi. U mnie to działa IDEALNIE.

Możesz użyć Cast (), aby zmienić go na IList, a następnie użyć OrderBy ():

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

GDZIE T to typ np. Model.Employee lub Plugin.ContactService.Shared.Contact

Następnie możesz użyć pętli for i jej DONE.

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

    foreach (var item in ordered)
    {
       ContactItems.Add(item);
    }
Momodu Deen Swarray
źródło
-1

Przekonwertuj swoją kolekcję IListna List<T>lub inną ogólną kolekcję, a następnie możesz łatwo przeszukiwać / sortować ją za pomocą System.Linqprzestrzeni nazw (dostarczy kilka metod rozszerzających)

lubos hasko
źródło
9
IList<T>implementuje IEnumerable<T>i dlatego nie trzeba go konwertować, aby używać operacji Linq.
Steve Guidi