Comment implémenter IEnumerable <T>

124

Je sais comment implémenter le IEnumerable non générique, comme ceci:

using System;
using System.Collections;

namespace ConsoleApplication33
{
    class Program
    {
        static void Main(string[] args)
        {
            MyObjects myObjects = new MyObjects();
            myObjects[0] = new MyObject() { Foo = "Hello", Bar = 1 };
            myObjects[1] = new MyObject() { Foo = "World", Bar = 2 };

            foreach (MyObject x in myObjects)
            {
                Console.WriteLine(x.Foo);
                Console.WriteLine(x.Bar);
            }

            Console.ReadLine();
        }
    }

    class MyObject
    {
        public string Foo { get; set; }
        public int Bar { get; set; }
    }

    class MyObjects : IEnumerable
    {
        ArrayList mylist = new ArrayList();

        public MyObject this[int index]
        {
            get { return (MyObject)mylist[index]; }
            set { mylist.Insert(index, value); }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return mylist.GetEnumerator();
        }
    }
}

Cependant, je remarque également que IEnumerable a une version générique IEnumerable<T>, mais je ne peux pas comprendre comment l'implémenter.

Si j'ajoute using System.Collections.Generic;à mes directives d'utilisation, puis que je change:

class MyObjects : IEnumerable

à:

class MyObjects : IEnumerable<MyObject>

Et puis faites un clic droit sur IEnumerable<MyObject>et sélectionnez Implement Interface => Implement Interface, Visual Studio ajoute utilement le bloc de code suivant:

IEnumerator<MyObject> IEnumerable<MyObject>.GetEnumerator()
{
    throw new NotImplementedException();
}

Le retour de l'objet IEnumerable non générique à partir de la GetEnumerator();méthode ne fonctionne pas cette fois, alors que dois-je mettre ici? La CLI ignore maintenant l'implémentation non générique et se dirige directement vers la version générique lorsqu'elle essaie d'énumérer via mon tableau pendant la boucle foreach.

JMK
la source

Réponses:

149

Si vous choisissez d'utiliser une collection générique, comme au List<MyObject>lieu de ArrayList, vous constaterez que le List<MyObject>fournira à la fois des énumérateurs génériques et non génériques que vous pouvez utiliser.

using System.Collections;

class MyObjects : IEnumerable<MyObject>
{
    List<MyObject> mylist = new List<MyObject>();

    public MyObject this[int index]  
    {  
        get { return mylist[index]; }  
        set { mylist.Insert(index, value); }  
    } 

    public IEnumerator<MyObject> GetEnumerator()
    {
        return mylist.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}
Monroe Thomas
la source
1
Dans l'implémentation non générique, y a-t-il une différence entre le retour this.GetEnumerator()et le simple retour GetEnumerator()?
Tanner Swett
1
@TannerSwett Il n'y a aucune différence.
Monroe Thomas
Vous pourriez aussi bien hériter de Collection<MyObject>et vous n'aurez pas à écrire de code personnalisé.
John Alexiou
@ ja72 Que faire si vous héritez déjà d'une autre classe de base et que vous ne pouvez pas hériter de Collection <MyObject>?
Monroe Thomas
1
@MonroeThomas - alors vous devez encapsuler List<T>et implémenter IEnumerable<T>comme indiqué dans votre réponse.
John Alexiou
69

Vous ne voulez probablement pas une implémentation explicite de IEnumerable<T>(ce que vous avez montré).

Le modèle habituel est d'utiliser IEnumerable<T>des GetEnumeratordans l'implémentation explicite de IEnumerable:

class FooCollection : IEnumerable<Foo>, IEnumerable
{
    SomeCollection<Foo> foos;

    // Explicit for IEnumerable because weakly typed collections are Bad
    System.Collections.IEnumerator IEnumerable.GetEnumerator()
    {
        // uses the strongly typed IEnumerable<T> implementation
        return this.GetEnumerator();
    }

    // Normal implementation for IEnumerable<T>
    IEnumerator<Foo> GetEnumerator()
    {
        foreach (Foo foo in this.foos)
        {
            yield return foo;
            //nb: if SomeCollection is not strongly-typed use a cast:
            // yield return (Foo)foo;
            // Or better yet, switch to an internal collection which is
            // strongly-typed. Such as List<T> or T[], your choice.
        }

        // or, as pointed out: return this.foos.GetEnumerator();
    }
}
user7116
la source
2
C'est une bonne solution si SomeCollection <T> n'implémente pas déjà IEnumerable <T>, ou si vous devez transformer ou effectuer un autre traitement sur chaque élément avant qu'il ne soit renvoyé dans la séquence d'énumération. Si aucune de ces conditions n'est vraie, alors il est probablement plus efficace de simplement renvoyer this.foos.GetEnumerator ();
Monroe Thomas
@MonroeThomas: d'accord. Aucune idée de la collection utilisée par l'OP.
user7116
8
Bon point. Mais la mise en œuvre IEnumerableest redondante (en IEnumerable<T>hérite déjà).
rsenna
@rsenna c'est vrai, mais gardez à l'esprit que même les interfaces .NET comme IList implémentent des interfaces de manière redondante, c'est pour la lisibilité - interface publique IList: ICollection, IEnumerable
David Klempfner
@Backwards_Dave En fait, je pense (toujours) que cela rend moins lisible, car cela ajoute du bruit inutile, mais je comprends ce que vous avez dit et je pense que c'est une opinion valable.
rsenna
24

Pourquoi le faites-vous manuellement? yield returnautomatise l'ensemble du processus de gestion des itérateurs. (J'ai également écrit à ce sujet sur mon blog , y compris un aperçu du code généré par le compilateur).

Si vous voulez vraiment le faire vous-même, vous devez également renvoyer un énumérateur générique. Vous ne pourrez plus en utiliser ArrayListcar ce n'est pas générique. Changez-le à la List<MyObject>place. Cela suppose bien sûr que vous n'avez que des objets de type MyObject(ou des types dérivés) dans votre collection.

Anders Abel
la source
2
+1, il convient de noter que le modèle préféré est d'implémenter l'interface générique et d'implémenter explicitement l'interface non générique. Le rendement du rendement est la solution la plus naturelle à l'interface générique.
user7116
5
À moins que l'OP ne fasse un traitement dans l'énumérateur, l'utilisation de yield return ajoute simplement la surcharge d'une autre machine à états. L'OP doit simplement renvoyer un énumérateur fourni par une collection générique sous-jacente.
Monroe Thomas
@MonroeThomas: Vous avez raison. Je n'ai pas lu la question très attentivement avant d'écrire sur yield return. J'ai supposé à tort qu'il y avait un traitement personnalisé.
Anders Abel
4

Si vous travaillez avec des génériques, utilisez List au lieu de ArrayList. La liste a exactement la méthode GetEnumerator dont vous avez besoin.

List<MyObject> myList = new List<MyObject>();
Amiram Korach
la source
0

faire mylist en a List<MyObject>, est une option

ColWhi
la source
0

Notez que le IEnumerable<T>déjà implémenté par le System.Collectionsdonc une autre approche est de dériver votre MyObjectsclasse System.Collectionscomme classe de base ( documentation ):

System.Collections: fournit la classe de base pour une collection générique.

Nous pouvons faire plus tard notre propre implemenation de passer outre les virtuels System.Collectionsméthodes pour fournir un comportement personnalisé (uniquement pour ClearItems, InsertItem, RemoveItemet SetItemainsi Equals, GetHashCodeet ToStringde Object). Contrairement au List<T>qui n'est pas conçu pour être facilement extensible.

Exemple:

public class FooCollection : System.Collections<Foo>
{
    //...
    protected override void InsertItem(int index, Foo newItem)
    {
        base.InsertItem(index, newItem);     
        Console.Write("An item was successfully inserted to MyCollection!");
    }
}

public static void Main()
{
    FooCollection fooCollection = new FooCollection();
    fooCollection.Add(new Foo()); //OUTPUT: An item was successfully inserted to FooCollection!
}

Veuillez noter que la conduite à partir de collectionrecommandé uniquement dans le cas où un comportement de collecte personnalisé est nécessaire, ce qui se produit rarement. voir l' utilisation .

Shahar Shokrani
la source