Wie implementiere ich IEnumerable <T>?

124

Ich weiß, wie man die nicht generische IEnumerable wie folgt implementiert:

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

Ich stelle jedoch auch fest, dass IEnumerable eine generische Version hat IEnumerable<T>, aber ich kann nicht herausfinden, wie ich sie implementieren soll.

Wenn ich using System.Collections.Generic;meine using-Direktiven hinzufüge und dann ändere:

class MyObjects : IEnumerable

zu:

class MyObjects : IEnumerable<MyObject>

Klicken Sie dann mit der rechten Maustaste auf IEnumerable<MyObject>und wählen Sie Implement Interface => Implement Interface, Visual Studio fügt den folgenden Codeblock hilfreich hinzu:

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

Das Zurückgeben des nicht generischen IEnumerable-Objekts aus der GetEnumerator();Methode funktioniert diesmal nicht. Was soll ich hier einfügen ? Die CLI ignoriert jetzt die nicht generische Implementierung und geht direkt zur generischen Version, wenn sie versucht, während der foreach-Schleife durch mein Array aufzulisten.

JMK
quelle

Antworten:

149

Wenn Sie sich für die Verwendung einer generischen Sammlung entscheiden, z. B. List<MyObject>anstelle von ArrayList, werden Sie feststellen, dass List<MyObject>diese sowohl generische als auch nicht generische Enumeratoren bereitstellt, die Sie verwenden können.

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
quelle
1
Gibt es in der nicht generischen Implementierung einen Unterschied zwischen der Rückgabe this.GetEnumerator()und der einfachen Rückgabe GetEnumerator()?
Tanner Swett
1
@TannerSwett Es gibt keinen Unterschied.
Monroe Thomas
Sie können genauso gut von erben Collection<MyObject>und müssen dann keinen benutzerdefinierten Code schreiben.
John Alexiou
@ ja72 Was ist, wenn Sie bereits von einer anderen Basisklasse erben und nicht von Collection <MyObject> erben können?
Monroe Thomas
1
@MonroeThomas - dann müssen Sie wie in Ihrer Antwort gezeigt umbrechen List<T>und implementieren IEnumerable<T>.
John Alexiou
69

Sie möchten wahrscheinlich keine explizite Implementierung von IEnumerable<T>(was Sie gezeigt haben).

Das übliche Muster ist die Verwendung von IEnumerable<T>'s GetEnumeratorbei der expliziten Implementierung von 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
quelle
2
Dies ist eine gute Lösung, wenn SomeCollection <T> IEnumerable <T> noch nicht implementiert oder wenn Sie jedes Element transformieren oder anderweitig verarbeiten müssen, bevor es in der Aufzählungssequenz zurückgegeben wird. Wenn keine dieser Bedingungen erfüllt ist, ist es wahrscheinlich effizienter, this.foos.GetEnumerator () zurückzugeben.
Monroe Thomas
@MonroeThomas: vereinbart. Keine Ahnung, welche Sammlung das OP verwendet.
user7116
8
Guter Punkt. Die Implementierung IEnumerableist jedoch redundant ( IEnumerable<T>erbt bereits davon).
Rsenna
@rsenna das ist wahr, aber denken Sie daran, auch .NET-Schnittstellen wie IList implementieren redundant Schnittstellen, es ist für die Lesbarkeit - öffentliche Schnittstelle IList: ICollection, IEnumerable
David Klempfner
@Backwards_Dave Ich denke tatsächlich (noch), dass es weniger lesbar ist, da es unnötiges Rauschen hinzufügt, aber ich verstehe, was Sie gesagt haben, und denke, dass es eine gültige Meinung ist.
Rsenna
24

Warum machst du das manuell? yield returnautomatisiert den gesamten Prozess der Handhabung von Iteratoren. (Ich habe auch in meinem Blog darüber geschrieben , einschließlich eines Blicks auf den vom Compiler generierten Code).

Wenn Sie es wirklich selbst tun möchten, müssen Sie auch einen generischen Enumerator zurückgeben. Sie können ArrayListkein mehr verwenden, da dies nicht generisch ist. Ändern Sie es List<MyObject>stattdessen in a. Dies setzt natürlich voraus, dass MyObjectIhre Sammlung nur Objekte vom Typ (oder abgeleitete Typen) enthält.

Anders Abel
quelle
2
+1 sollte beachtet werden, dass das bevorzugte Muster darin besteht, die generische Schnittstelle zu implementieren und die nicht generische Schnittstelle explizit zu implementieren. Die Rendite ist die natürlichste Lösung für die generische Schnittstelle.
user7116
5
Sofern das OP keine Verarbeitung im Enumerator durchführt, erhöht die Verwendung von Yield Return nur den Overhead einer anderen Zustandsmaschine. Das OP sollte nur einen Enumerator zurückgeben, der von einer zugrunde liegenden generischen Sammlung bereitgestellt wird.
Monroe Thomas
@MonroeThomas: Du hast recht. Ich habe die Frage nicht sehr sorgfältig gelesen, bevor ich darüber geschrieben habe yield return. Ich habe fälschlicherweise angenommen, dass es eine benutzerdefinierte Verarbeitung gibt.
Anders Abel
4

Wenn Sie mit Generika arbeiten, verwenden Sie List anstelle von ArrayList. Die Liste enthält genau die GetEnumerator-Methode, die Sie benötigen.

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

Mylist zu einer machen List<MyObject>, ist eine Option

ColWhi
quelle
0

Beachten Sie, dass der IEnumerable<T>bereits implementierte System.CollectionsAnsatz darin besteht, Ihre MyObjectsKlasse System.Collectionsals Basisklasse abzuleiten ( Dokumentation ):

System.Collections: Stellt die Basisklasse für eine generische Sammlung bereit.

Wir können später unsere eigene Implemenation machen die virtuellen außer Kraft zu setzen System.CollectionsMethoden individuelle Verhalten , um (nur für ClearItems, InsertItem, RemoveItem, und SetItemzusammen mit Equals, GetHashCodeund ToStringab Object). Im Gegensatz zu dem, List<T>der nicht leicht erweiterbar ist.

Beispiel:

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

Bitte beachten Sie, dass das Fahren von collectionempfohlen nur dann empfohlen wird, wenn ein benutzerdefiniertes Erfassungsverhalten erforderlich ist, was selten vorkommt. siehe Verwendung .

Shahar Shokrani
quelle