Jak zaimplementować IEnumerable <T>

124

Wiem, jak zaimplementować nieogólne IEnumerable, na przykład:

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

Jednak zauważam również, że IEnumerable ma wersję ogólną, IEnumerable<T> , ale nie mogę dowiedzieć się, jak ją zaimplementować.

Jeśli dodam using System.Collections.Generic;do moich dyrektyw using, a następnie zmienię:

class MyObjects : IEnumerable

do:

class MyObjects : IEnumerable<MyObject>

Następnie kliknij prawym przyciskiem myszy IEnumerable<MyObject>i wybierz Implement Interface => Implement Interface, Visual Studio z pomocą dodaje następujący blok kodu:

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

Zwracanie nieogólnego obiektu IEnumerable z GetEnumerator();metody nie działa tym razem, więc co mam tutaj umieścić? Interfejs CLI ignoruje teraz nieogólną implementację i kieruje się bezpośrednio do wersji ogólnej, gdy próbuje wyliczyć moją tablicę podczas pętli foreach.

JMK
źródło

Odpowiedzi:

149

Jeśli zdecydujesz się użyć kolekcji ogólnej, takiej jak List<MyObject>zamiast ArrayList, przekonasz się, że List<MyObject>będą one zawierać zarówno ogólne, jak i nieogólne moduły wyliczające, których możesz użyć.

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
źródło
1
Czy w implementacji nieogólnej istnieje różnica między zwracaniem this.GetEnumerator()a zwykłym zwracaniem GetEnumerator()?
Tanner Swett
1
@TannerSwett Nie ma różnicy.
Monroe Thomas
Równie dobrze Collection<MyObject>możesz dziedziczyć po, a wtedy nie będziesz musiał pisać żadnego niestandardowego kodu.
John Alexiou
@ ja72 Co się stanie, jeśli już dziedziczenie z innej klasy bazowej i nie możesz dziedziczyć z kolekcji <MyObject>?
Monroe Thomas
1
@MonroeThomas - wtedy musisz owinąć List<T>i wdrożyć, IEnumerable<T>jak pokazano w Twojej odpowiedzi.
John Alexiou,
69

Prawdopodobnie nie chcesz jawnej implementacji IEnumerable<T>(co pokazałeś).

Zazwyczaj wzór jest do użytku IEnumerable<T>„s GetEnumeratorw wyraźnej realizacji 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
źródło
2
Jest to dobre rozwiązanie, jeśli SomeCollection <T> nie implementuje jeszcze IEnumerable <T> lub jeśli musisz przekształcić lub wykonać inne przetwarzanie na każdym elemencie, zanim zostanie on zwrócony w sekwencji wyliczenia. Jeśli żaden z tych warunków nie jest spełniony, prawdopodobnie bardziej efektywne będzie po prostu zwrócenie this.foos.GetEnumerator ();
Monroe Thomas
@MonroeThomas: zgodził się. Nie mam pojęcia, jakiej kolekcji używa OP.
user7116
8
Słuszna uwaga. Ale implementacja IEnumerablejest zbędna ( IEnumerable<T>już po niej dziedziczy).
rsenna
@rsenna to prawda, ale pamiętaj, że nawet interfejsy .NET, takie jak IList, redundantnie implementują interfejsy, to dla czytelności - interfejs publiczny IList: ICollection, IEnumerable
David Klempfner
@Backwards_Dave Właściwie (nadal) uważam, że jest mniej czytelny, ponieważ dodaje niepotrzebny szum, ale rozumiem, co powiedziałeś, i myślę, że to ważna opinia.
rsenna
24

Dlaczego robisz to ręcznie? yield returnautomatyzuje cały proces obsługi iteratorów. (Pisałem też o tym na swoim blogu , włączając w to spojrzenie na kod wygenerowany przez kompilator).

Jeśli naprawdę chcesz to zrobić sam, musisz również zwrócić ogólny moduł wyliczający. Nie będziesz już mógł używać ArrayListżadnego, ponieważ nie jest to typowe. List<MyObject>Zamiast tego zmień go na . To oczywiście zakłada, że MyObjectw swojej kolekcji masz tylko obiekty typu (lub typy pochodne).

Anders Abel
źródło
2
+1, należy zauważyć, że preferowanym wzorcem jest implementacja interfejsu ogólnego i jawna implementacja interfejsu innego niż ogólny. Zwrot zysków jest najbardziej naturalnym rozwiązaniem ogólnego interfejsu.
user7116
5
O ile OP nie będzie wykonywał jakiegoś przetwarzania w module wyliczającym, użycie zwrotu zysku po prostu dodaje narzut innego automatu stanowego. OP powinien po prostu zwrócić moduł wyliczający dostarczony przez bazową kolekcję ogólną.
Monroe Thomas
@MonroeThomas: Masz rację. Nie przeczytałem dokładnie pytania, zanim o nim napisałem yield return. Błędnie założyłem, że było jakieś niestandardowe przetwarzanie.
Anders Abel
4

Jeśli pracujesz z rodzajami, użyj listy zamiast ArrayList. Lista zawiera dokładnie taką metodę GetEnumerator, jakiej potrzebujesz.

List<MyObject> myList = new List<MyObject>();
Amiram Korach
źródło
0

zmienić mylist w a List<MyObject>, jest jedną z opcji

ColWhi
źródło
0

Zauważ, że IEnumerable<T>już zaimplementowane przez System.Collectionsto inne podejście polega na wyprowadzeniu Twojej MyObjectsklasy z System.Collectionsklasy bazowej ( dokumentacja ):

System.Collections: dostarcza klasę bazową dla kolekcji ogólnej.

Możemy później uczynić nasz własny implementacja zastąpić wirtualne System.Collectionsmetody zapewniające zachowanie niestandardowe (tylko ClearItems, InsertItem, RemoveItem, a SetItemwraz z Equals, GetHashCodei ToStringod Object). W przeciwieństwie do tego, List<T>który nie jest zaprojektowany tak, aby był łatwo rozszerzalny.

Przykład:

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

Należy pamiętać, że jazda z collectionzalecana jest tylko w przypadku, gdy potrzebne jest niestandardowe zachowanie kolekcji, co rzadko się zdarza. zobacz użycie .

Shahar Shokrani
źródło