Come implementare IEnumerable <T>


124

So come implementare l'IEnumerable non generico, in questo modo:

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

Tuttavia noto anche che IEnumerable ha una versione generica IEnumerable<T>, ma non riesco a capire come implementarla.

Se aggiungo using System.Collections.Generic;alle mie direttive using e poi cambio:

class MyObjects : IEnumerable

per:

class MyObjects : IEnumerable<MyObject>

Quindi fare clic con il pulsante destro del mouse IEnumerable<MyObject>e selezionare Implement Interface => Implement Interface, Visual Studio aggiunge utilmente il seguente blocco di codice:

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

Restituire l'oggetto IEnumerable non generico dal GetEnumerator();metodo non funziona questa volta, quindi cosa metto qui? La CLI ora ignora l'implementazione non generica e si dirige direttamente alla versione generica quando tenta di enumerare il mio array durante il ciclo foreach.

Risposte:


149

Se scegli di utilizzare una raccolta generica, ad esempio List<MyObject>invece di ArrayList, scoprirai che List<MyObject>fornirà enumeratori generici e non generici che puoi utilizzare.

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

1
Nell'implementazione non generica, c'è differenza tra restituire this.GetEnumerator()e semplicemente restituire GetEnumerator()?
Tanner Swett

1
@TannerSwett Non c'è differenza.
Monroe Thomas

Potresti anche ereditare da Collection<MyObject>e quindi non dovrai scrivere alcun codice personalizzato.
John Alexiou

@ ja72 Cosa succede se erediti già da un'altra classe base e non puoi ereditare da Collection <MyObject>?
Monroe Thomas

1
@MonroeThomas - quindi devi avvolgere List<T>e implementare IEnumerable<T>come mostrato nella tua risposta.
John Alexiou

69

Probabilmente non vuoi un'implementazione esplicita di IEnumerable<T>(che è ciò che hai mostrato).

Lo schema usuale è usare IEnumerable<T>'s GetEnumeratornell'esplicita implementazione di 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();
    }
}

2
Questa è una buona soluzione se SomeCollection <T> non implementa già IEnumerable <T> o se è necessario trasformare o eseguire altre elaborazioni su ogni elemento prima che venga restituito nella sequenza di enumerazione. Se nessuna di queste condizioni è vera, allora è probabilmente più efficiente restituire semplicemente this.foos.GetEnumerator ();
Monroe Thomas

@MonroeThomas: d'accordo. Non ho idea di quale raccolta stia usando l'OP.
user7116

8
Buon punto. Ma l'implementazione IEnumerableè ridondante ( IEnumerable<T>eredita già da essa).
rsenna

@rsenna è vero, tuttavia tieni presente che anche le interfacce .NET come IList implementano interfacce in modo ridondante, è per la leggibilità - interfaccia pubblica IList: ICollection, IEnumerable
David Klempfner

@Backwards_Dave In realtà (ancora) penso che renda meno leggibile, poiché aggiunge rumore non necessario, ma capisco quello che hai detto e penso che sia un'opinione valida.
rsenna

24

Perché lo fai manualmente? yield returnautomatizza l'intero processo di gestione degli iteratori. (Ne ho anche scritto sul mio blog , incluso uno sguardo al codice generato dal compilatore).

Se vuoi davvero farlo da solo, devi restituire anche un enumeratore generico. Non potrai più utilizzarne uno ArrayListpoiché non è generico. Cambialo in un List<MyObject>invece. Ciò ovviamente presuppone che MyObjectnella tua raccolta siano presenti solo oggetti di tipo (o tipi derivati).


2
+1, va notato che il modello preferito è implementare l'interfaccia generica e implementare esplicitamente l'interfaccia non generica. Il rendimento del rendimento è la soluzione più naturale per l'interfaccia generica.
user7116

5
A meno che l'OP non stia eseguendo un'elaborazione nell'enumeratore, l'uso di yield return aggiunge semplicemente l'overhead di un'altra macchina a stati. L'OP dovrebbe semplicemente restituire un enumeratore fornito da una raccolta generica sottostante.
Monroe Thomas

@MonroeThomas: hai ragione. Non ho letto la domanda molto attentamente prima di scriverne yield return. Ho pensato erroneamente che ci fossero delle elaborazioni personalizzate.
Anders Abel

4

Se lavori con i generici, usa List invece di ArrayList. La lista ha esattamente il metodo GetEnumerator di cui hai bisogno.

List<MyObject> myList = new List<MyObject>();

0

trasforma la mia lista in una List<MyObject>, è un'opzione


0

Nota che il IEnumerable<T>già implementato da System.Collectionsquindi un altro approccio è derivare la tua MyObjectsclasse da System.Collectionsuna classe base ( documentazione ):

System.Collections: fornisce la classe base per una raccolta generica.

Dopo possiamo fare la nostra implemenation per sovrascrivere il virtuali System.Collectionsmetodi per fornire un comportamento personalizzato (solo per ClearItems, InsertItem, RemoveItem, e SetIteminsieme Equals, GetHashCodee ToStringda Object). A differenza del List<T>quale non è progettato per essere facilmente estensibile.

Esempio:

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

Si noti che la guida da è collectionconsigliata solo nel caso in cui sia necessario un comportamento di raccolta personalizzato, cosa che accade raramente. vedi utilizzo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.