Сортировка списка IList в C #


86

Итак, сегодня я столкнулся с интересной проблемой. У нас есть веб-служба WCF, которая возвращает список IList. Ничего особенного, пока я не разобрался.

Оказывается, интерфейс IList не имеет встроенного метода сортировки.

В конце концов, я ArrayList.Adapter(list).Sort(new MyComparer())решил использовать этот метод для решения проблемы, но мне это показалось немного "гетто".

Я играл с написанием метода расширения, а также с наследованием от IList и реализацией моего собственного метода Sort (), а также с приведением к списку, но ни один из них не казался слишком элегантным.

Итак, мой вопрос: есть ли у кого-нибудь элегантное решение для сортировки IList


Зачем вообще нужно возвращать IList? Из службы WCF?
DaeMoohn

Ответы:


55

Как насчет использования LINQ To Objects для сортировки за вас?

Допустим, у вас есть а IList<Car>, а у машины есть Engineсобственность, я думаю, вы можете отсортировать ее следующим образом:

from c in list
orderby c.Engine
select c;

Изменить: вам нужно быстро получить ответы здесь. Поскольку я представил несколько иной синтаксис к другим ответам, я оставлю свой ответ, однако другие представленные ответы одинаково верны.


3
Он создаст новый перечислимый объект, что может быть нежелательно в некоторых сценариях. Вы не можете отсортировать IList <T> на месте через интерфейс, кроме как с помощью метода ArrayList.Adapter, насколько мне известно.
Танвир Бадар

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>нельзя отсортировать элементы на месте без повторного добавления элементов с помощью воображаемого статического метода?
OR Mapper

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

1
ComparisonComparerКласс не является необходимым. Comparer<T>.Create(comparison)Вместо этого вы можете использовать стандартный статический метод .
лайнпогл

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интерфейс.


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

Что, если вы хотите использовать DESC вместо ASC?
sam byte

1

Нашел эту ветку, пока искал решение точной проблемы, описанной в исходном сообщении. Однако ни один из ответов полностью не соответствовал моей ситуации. Ответ Броуди был довольно близок. Вот моя ситуация и решение, которое я нашел.

У меня есть два списка IList одного и того же типа, возвращенные NHibernate, и они объединили два списка IList в один, отсюда и необходимость сортировки.

Как сказал Броуди, я реализовал 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

Список A B C

Iist еще раз 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.Employee или Plugin.ContactService.Shared.Contact

Затем вы можете использовать цикл for и его DONE.

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

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

-1

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


9
IList<T>реализует IEnumerable<T>и, следовательно, не требует преобразования для использования операций Linq.
Стив Гуиди,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.