Tri d'un IList en C #

86

Je suis donc tombé sur un problème intéressant aujourd'hui. Nous avons un service Web WCF qui renvoie un IList. Pas vraiment grand-chose jusqu'à ce que je veuille régler ça.

Il s'avère que l'interface IList n'a pas de méthode de tri intégrée.

J'ai fini par utiliser la ArrayList.Adapter(list).Sort(new MyComparer())méthode pour résoudre le problème, mais cela me semblait un peu «ghetto».

J'ai joué avec l'écriture d'une méthode d'extension, également avec l'héritage de IList et l'implémentation de ma propre méthode Sort () ainsi que le cast dans une liste, mais aucun de ceux-ci ne semblait trop élégant.

Ma question est donc la suivante: est-ce que quelqu'un a une solution élégante pour trier un IList

lomaxx
la source
Pourquoi retourneriez-vous un IList en premier lieu? D'un service WCF?
DaeMoohn

Réponses:

54

Que diriez-vous d'utiliser LINQ To Objects pour trier pour vous?

Disons que vous avez un IList<Car>, et que la voiture a une Enginepropriété, je pense que vous pouvez trier comme suit:

from c in list
orderby c.Engine
select c;

Edit: Vous devez être rapide pour obtenir des réponses ici. Comme j'ai présenté une syntaxe légèrement différente des autres réponses, je laisserai ma réponse - cependant, les autres réponses présentées sont également valables.

Brad Leach
la source
3
Cela créera un nouvel énumérable, ce qui peut ne pas être souhaitable dans certains scénarios. Vous ne pouvez pas trier un IList <T> sur place via l'interface, sauf en utilisant la méthode ArrayList.Adapter à ma connaissance.
Tanveer Badar
67

Vous pouvez utiliser 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
la source
61

Cette question m'a inspiré pour écrire un article de blog: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

Je pense que, idéalement, le .NET Framework inclurait une méthode de tri statique qui accepte un IList <T>, mais la meilleure chose à faire est de créer votre propre méthode d'extension. Il n'est pas trop difficile de créer quelques méthodes qui vous permettront de trier un IList <T> comme vous le feriez pour un List <T>. En prime, vous pouvez surcharger la méthode d'extension LINQ OrderBy en utilisant la même technique, de sorte que que vous utilisiez List.Sort, IList.Sort ou IEnumerable.OrderBy, vous puissiez utiliser exactement la même syntaxe.

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

Avec ces extensions, triez votre IList comme vous le feriez pour une liste:

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

Il y a plus d'informations dans le post: http://blog.velir.com/index.php/2011/02/17/ilistt-sorting-a-better-way/

David Mills
la source
1
La bonne approche aurait vraiment été d'offrir une ISortableList<T>interface (avec des méthodes pour trier une partie de la liste en utilisant un comparateur particulier), de l' List<T>implémenter et d'avoir une méthode statique qui pourrait en trier IList<T>en vérifiant si elle est implémentée ISortableList<T>et, sinon, en le copiant dans un tableau, en le triant, en effaçant IList<T>et en ajoutant à nouveau les éléments.
supercat du
4
Merveilleuse réponse! Cependant, une mise en garde: cette approche suppose que le IList<T> listpeut être transtypé vers l' IListinterface non générique . Si vous codez votre propre classe implémentant l' IList<T>interface, assurez-vous que vous implémentez également l' IListinterface non générique , sinon le code échouera avec une exception de conversion de classe.
sstan
1
@supercat: Que pourrait ISortableList<T>offrir ce qui n'est pas déjà fait IList<T>? Ou, demandé différemment, pourquoi ne pourrait-on pas IList<T>trier un sur place sans rajouter les éléments par votre méthode statique imaginée?
OR Mapper
@ORMapper: Si une liste utilise un tableau comme magasin de sauvegarde (commun, mais pas obligatoire), une routine de tri qui accède directement aux éléments du tableau peut être beaucoup plus rapide qu'une routine qui doit passer par l' IList<T>interface pour accéder à chaque élément. La différence de vitesse est suffisamment grande pour que dans de nombreux cas, il puisse être plus rapide de copier une liste dans un tableau, de trier le tableau et de recopier la liste, que d'essayer de mettre en place une routine de tri pour traiter la liste.
supercat du
1
La ComparisonComparerclasse n'est pas nécessaire. Vous pouvez utiliser la méthode statique standard à la Comparer<T>.Create(comparison)place.
linepogl
9

Vous allez devoir faire quelque chose comme ça je pense (le convertir en un type plus concret).

Peut-être le prendre dans une liste de T plutôt que dans ArrayList, de sorte que vous obteniez la sécurité de type et plus d'options pour la façon dont vous implémentez le comparateur.

Léon Bambrick
la source
4

La réponse acceptée par @DavidMills est assez bonne, mais je pense qu'elle peut être améliorée. D'une part, il n'est pas nécessaire de définir la ComparisonComparer<T>classe lorsque le framework inclut déjà une méthode statique Comparer<T>.Create(Comparison<T>). Cette méthode peut être utilisée pour créer un fichier IComparisonà la volée.

En outre, il jette IList<T>à IListqui a le potentiel d'être dangereux. Dans la plupart des cas que j'ai vus, List<T>quels outils IListsont utilisés dans les coulisses pour implémenterIList<T> , mais ce n'est pas garanti et peut conduire à un code fragile.

Enfin, la List<T>.Sort()méthode surchargée a 4 signatures et seulement 2 d'entre elles sont implémentées.

  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 ci-dessous implémente les 4 List<T>.Sort()signatures pour l' 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];
        }
    }
}

Usage:

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'idée ici est de tirer parti de la fonctionnalité du sous-jacent List<T>pour gérer le tri dans la mesure du possible. Encore une fois, la plupart des IList<T>implémentations que j'ai vues l'utilisent. Dans le cas où la collection sous-jacente est d'un type différent, revenez à la création d'une nouvelle instance de List<T>avec des éléments de la liste d'entrée, utilisez-la pour effectuer le tri, puis copiez les résultats dans la liste d'entrée. Cela fonctionnera même si la liste d'entrée n'implémente pas l' IListinterface.

Dana
la source
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
la source
Et si vous voulez que ce soit DESC au lieu de ASC?
sam byte il y a
1

J'ai trouvé ce fil pendant que je cherchais une solution au problème exact décrit dans le message original. Aucune des réponses ne répondait entièrement à ma situation, cependant. La réponse de Brody était assez proche. Voici ma situation et la solution que j'y ai trouvée.

J'ai deux ILists du même type retournés par NHibernate et j'ai émergé les deux IList en un seul, d'où la nécessité de les trier.

Comme Brody l'a dit, j'ai implémenté un ICompare sur l'objet (ReportFormat) qui est le type de mon IList:

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

Je convertis ensuite l'IList fusionné en un tableau du même type:

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

Puis triez le tableau:

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

Puisque le tableau unidimensionnel implémente l'interface System.Collections.Generic.IList<T>, le tableau peut être utilisé comme l'IList d'origine.

John
la source
1

Utile pour le tri par grille, cette méthode trie la liste en fonction des noms de propriété. Comme suivez l'exemple.

    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
la source
0

Voici un exemple utilisant le typage plus fort. Je ne sais pas si c'est forcément le meilleur moyen.

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 fonction Cast est juste une réimplémentation de la méthode d'extension fournie avec 3.5 écrite comme une méthode statique normale. C'est assez laid et bavard malheureusement.

ICR
la source
0

Dans VS2008, lorsque je clique sur la référence de service et que je sélectionne "Configurer la référence de service", il y a une option pour choisir comment le client désérialise les listes renvoyées par le service.

Notamment, je peux choisir entre System.Array, System.Collections.ArrayList et System.Collections.Generic.List

Amy B
la source
0
using System.Linq;

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

C'est joli! Ghetto.


la source
0

J'ai trouvé un bon article à ce sujet et j'ai pensé partager. Vérifiez le ici

Fondamentalement.

Vous pouvez créer la classe suivante et les 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);
}
}

Ensuite, si vous avez un IList, vous pouvez le trier comme ceci.

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

Mais consultez ce site pour plus d'informations ... Consultez-le ICI

Mizipzor
la source
0

Est-ce une solution valable?

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

Le résultat était: IList B A C

Liste A B C

IList à nouveau A B C

Yoav
la source
1
Valide s'il s'agit vraiment d'un List <T>. Dans certains cas, vous avez d'autres types implémentant IList <T> (par exemple, un tableau simple) où le downcast ne fonctionnerait pas. Dommage que la méthode Sort () ne soit pas une méthode d'extension à IList <T>.
Cygon
0

Cela semble BEAUCOUP PLUS SIMPLE si vous me demandez. Cela fonctionne parfaitement pour moi.

Vous pouvez utiliser Cast () pour le changer en IList puis utiliser OrderBy ():

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

WHERE T est le type par exemple. Model.Employee ou Plugin.ContactService.Shared.Contact

Ensuite, vous pouvez utiliser une boucle for et son DONE.

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

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

Convertissez votre IListdans List<T>ou d' une autre collection générique et vous pouvez facilement requête / trier à l'aide d' System.Linqespace de noms (il fournira tas de méthodes d'extension)

lubos hasko
la source
9
IList<T>implémente IEnumerable<T>et n'a donc pas besoin d'être converti pour utiliser les opérations Linq.
Steve Guidi