Сортування списку IL у C #


86

Тож сьогодні я натрапив на цікаву проблему. У нас є веб-служба WCF, яка повертає IList. Насправді це не велика справа, поки я не захотів це розібрати.

Виявляється, інтерфейс IList не має вбудованого методу сортування.

У підсумку я використав ArrayList.Adapter(list).Sort(new MyComparer())метод для вирішення проблеми, але це мені просто здалося трохи «гетто».

Я погрався з написанням методу розширення, також успадкуванням від IList та реалізацією власного методу Sort (), а також приведенням до Списку, але жоден з них не здавався надмірно елегантним.

Отже, моє питання полягає в тому, чи є у когось елегантне рішення для сортування IList


Чому б ви взагалі повернули IList? Від послуги WCF?
DaeMoohn

Відповіді:


54

Як щодо використання LINQ To Objects для сортування для вас?

Скажімо, у вас є IList<Car>, і у машини була Engineвласність, я вважаю, ви можете відсортувати наступним чином:

from c in list
orderby c.Engine
select c;

Редагувати: Вам потрібно швидше отримати відповіді тут. Оскільки я подав дещо інший синтаксис для інших відповідей, я залишу свою відповідь, однак інші представлені відповіді однаково справедливі.


3
Це створить нову кількість, що може бути небажаним у деяких сценаріях. Ви не можете сортувати IList <T> на місці за допомогою інтерфейсу, за винятком використання методу ArrayList.Adapter, наскільки мені відомо.
Tanveer Badar

67

Ви можете використовувати LINQ:

using System.Linq;

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

61

Це запитання надихнуло мене написати допис у блозі: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

Я думаю, що в ідеалі .NET Framework включав би статичний метод сортування, який приймає IList <T>, але наступне найкраще - створити власний метод розширення. Не надто складно створити пару методів, які дозволять вам сортувати IList <T>, як це робить List <T>. Як бонус ви можете перевантажити метод розширення LINQ OrderBy, використовуючи ту саму техніку, так що, використовуючи List.Sort, IList.Sort або IEnumerable.OrderBy, ви можете використовувати точно такий самий синтаксис.

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

За допомогою цих розширень сортуйте свій IList так само, як і список:

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

Більше інформації в публікації: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/


1
Правильним підходом справді було б запропонувати ISortableList<T>інтерфейс (із методами сортування частини списку за допомогою певного порівняльника), List<T>реалізувати його та мати статичний метод, який міг би сортувати будь-який IList<T>, перевіряючи, чи він реалізований, ISortableList<T>а якщо ні, копіюючи його в масив, сортуючи, очищаючи IList<T>та повторно додаючи елементи.
supercat

4
Чудова відповідь! Однак, будьте обережні: цей підхід передбачає, що файл IList<T> listможе бути переданий не-загальному IListінтерфейсу. Якщо ви кодуєте власний клас, що реалізує IList<T>інтерфейс, переконайтеся, що ви також реалізуєте не загальний IListінтерфейс, інакше код не вдасться за винятком приведення класу.
sstan

1
@supercat: Що може ISortableList<T>запропонувати, чого ще немає IList<T>? Або, запитуючи по-іншому, чому неможливо IList<T>відсортувати на місці без повторного додавання елементів за допомогою вашого уявного статичного методу?
АБО Mapper

@ORMapper: Якщо список використовує масив як резервне сховище (загальне, але не обов’язкове), процедура сортування, яка безпосередньо отримує доступ до елементів масиву, може бути набагато швидшою, ніж та, яка повинна пройти через IList<T>інтерфейс для доступу до кожного елемента. Різниця в швидкості є достатньо великою, що в багатьох випадках може бути швидше скопіювати список у масив, відсортувати масив та скопіювати список назад, ніж намагатися виконати процедуру сортування списку на місці.
Supercat

1
ComparisonComparerКлас не є необхідним. Comparer<T>.Create(comparison)Натомість ви можете використовувати стандартний статичний метод .
linepogl

9

Вам доведеться зробити щось подібне, як я думаю (перетворити на більш конкретний тип).

Можливо, внесіть його до Списку T, а не до ArrayList, щоб ви отримали безпеку типу та більше варіантів того, як впроваджувати порівняльник.


4

Прийнята відповідь @DavidMills цілком гарна, але я думаю, що її можна вдосконалити. По-перше, немає необхідності визначати ComparisonComparer<T>клас, коли фреймворк вже включає статичний метод Comparer<T>.Create(Comparison<T>). Цей метод можна використовувати для створення IComparisonна льоту.

Крім того, це касти, IList<T>для IListяких потенційно небезпечно. У більшості випадків, які я бачив, List<T>який інструмент IListвикористовується за кулісами для реалізаціїIList<T> , але це не гарантується і може призвести до крихкого коду.

Нарешті, перевантажений List<T>.Sort()метод має 4 підписи, і лише 2 з них реалізовані.

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

У наведеному нижче класі реалізовані всі 4 List<T>.Sort()підписи для IList<T>інтерфейсу:

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

Використання:

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

Ідея тут полягає у використанні функціональних можливостей основного List<T>для обробки сортування, коли це можливо. Знову ж таки, більшість IList<T>реалізацій, які я бачив, використовують це. У тому випадку, коли основна колекція іншого типу, поверніться до створення нового екземпляра List<T>з елементами зі списку вводу, використовуйте його для сортування, а потім скопіюйте результати назад у список введення. Це спрацює, навіть якщо вхідний список не реалізує IListінтерфейс.


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

Знайшов цю тему, поки я шукав рішення точної проблеми, описаної в оригінальній публікації. Проте жодна з відповідей не відповідала моїй ситуації. Відповідь Броді була досить близькою. Ось моя ситуація та рішення, яке я знайшов.

У мене є два ІЛісти одного типу, повернуті NHibernate, і я об'єднав два Іллісти в один, звідси і необхідність сортування.

Як Броді сказав, я реалізував ICompare на об'єкті (ReportFormat), який є типом мого IList:

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

Потім я перетворюю об’єднаний IList в масив того ж типу:

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

Потім відсортуйте масив:

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

Оскільки одновимірний масив реалізує інтерфейс System.Collections.Generic.IList<T>, масив можна використовувати так само, як оригінальний IList.


1

Корисно для сортування в сітці, цей метод сортує список на основі імен властивостей. Як слід за прикладом.

    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

Ось приклад використання сильнішого набору тексту. Не впевнений, що це обов’язково найкращий спосіб.

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

Функція Cast - це лише реалізація методу розширення, який постачається з 3.5, записаним як звичайний статичний метод. Це досить потворно і багатослівно, на жаль.


0

У VS2008, коли я натискаю посилання на службу та вибираю «Налаштувати посилання на службу», є можливість вибрати, як клієнт десеріалізує списки, що повертаються із служби.

Примітно, що я можу вибирати між System.Array, System.Collections.ArrayList і System.Collections.Generic.List


0
using System.Linq;

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

Це гарно! Гетто.


0

Знайшов хороший допис з цього приводу і подумав поділитися. Перевірте це ТУТ

В основному.

Ви можете створити наступні класи та класи 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);
}
}

Тоді, якщо у вас є IList, ви можете відсортувати його так.

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

Але перевірте цей сайт для отримання додаткової інформації ... Перевірте його ТУТ


0

Це дійсне рішення?

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

Результат був: IList B A C

Список А Б В

IList знову A B C


1
Дійсний, якщо це насправді List <T>. У деяких випадках у вас є інші типи, що реалізують IList <T> (наприклад, звичайний масив), де зниження не працює. Шкода, що метод Sort () не є методом розширення до IList <T>.
Cygon

0

Це виглядає НАБАГАТО ПРОСТО, якщо ви запитаєте мене. Це працює ВІДМІННО для мене.

Ви можете використовувати Cast (), щоб змінити його на IList, а потім використовувати OrderBy ():

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

ДЕ T - тип, наприклад. Model.E Employee або Plugin.ContactService.Shared.Contact

Тоді ви можете використовувати цикл for та його ГОТОВО.

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

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

-1

Перетворення IListINTO List<T>або іншу спільну колекцію , а потім ви можете легко запит / сортувати його з допомогою System.Linqпростору імен (він буде постачати купу методів розширення)


9
IList<T>реалізує IEnumerable<T>і тому не потребує перетворення для використання операцій Linq.
Steve Guidi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.