¿Cómo implemento IEnumerable <T>

124

Sé cómo implementar el IEnumerable no genérico, así:

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

Sin embargo, también noto que IEnumerable tiene una versión genérica IEnumerable<T>, pero no puedo entender cómo implementarla.

Si agrego using System.Collections.Generic;a mis directivas de uso y luego cambio:

class MyObjects : IEnumerable

a:

class MyObjects : IEnumerable<MyObject>

Y luego haga clic derecho IEnumerable<MyObject>y seleccione Implement Interface => Implement Interface, Visual Studio agrega útilmente el siguiente bloque de código:

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

Devolver el objeto IEnumerable no genérico del GetEnumerator();método no funciona esta vez, entonces, ¿qué pongo aquí? La CLI ahora ignora la implementación no genérica y se dirige directamente a la versión genérica cuando intenta enumerar a través de mi matriz durante el ciclo foreach.

JMK
fuente

Respuestas:

149

Si elige usar una colección genérica, como en List<MyObject>lugar de ArrayList, encontrará que List<MyObject>proporcionará enumeradores genéricos y no genéricos que puede usar.

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
fuente
1
En la implementación no genérica, ¿hay alguna diferencia entre regresar this.GetEnumerator()y simplemente regresar GetEnumerator()?
Tanner Swett
1
@TannerSwett No hay diferencia.
Monroe Thomas
También podría heredar de Collection<MyObject>y luego no tendrá que escribir ningún código personalizado.
John Alexiou
@ ja72 ¿Qué sucede si ya está heredando de otra clase base y no puede heredar de Collection <MyObject>?
Monroe Thomas
1
@MonroeThomas: luego debe ajustar List<T>e implementar IEnumerable<T>como se muestra en su respuesta.
John Alexiou
69

Probablemente no desee una implementación explícita de IEnumerable<T>(que es lo que ha mostrado).

El patrón habitual es usar IEnumerable<T>'s GetEnumeratoren la implementación explícita 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();
    }
}
usuario7116
fuente
2
Esta es una buena solución si SomeCollection <T> aún no implementa IEnumerable <T>, o si necesita transformar o realizar otro procesamiento en cada elemento antes de que se devuelva en la secuencia de enumeración. Si ninguna de estas condiciones es verdadera, entonces probablemente sea más eficiente simplemente devolver this.foos.GetEnumerator ();
Monroe Thomas
@MonroeThomas: de acuerdo. No tengo idea de qué colección está utilizando el OP.
user7116
8
Buen punto. Pero la implementación IEnumerablees redundante ( IEnumerable<T>ya se hereda de ella).
rsenna
@rsenna es cierto, sin embargo, tenga en cuenta que incluso las interfaces .NET como IList implementan interfaces de forma redundante, es para facilitar la lectura - interfaz pública IList: ICollection, IEnumerable
David Klempfner
@Backwards_Dave De hecho (todavía) creo que se vuelve menos legible, ya que agrega ruido innecesario, pero entiendo lo que dijiste y creo que es una opinión válida.
rsenna
24

¿Por qué lo haces manualmente? yield returnautomatiza todo el proceso de manejo de iteradores. (También escribí sobre eso en mi blog , incluyendo un vistazo al código generado por el compilador).

Si realmente desea hacerlo usted mismo, también debe devolver un enumerador genérico. Ya no podrá usar uno ArrayListya que no es genérico. Cámbielo a un List<MyObject>lugar. Eso, por supuesto, supone que solo tiene objetos de tipo MyObject(o tipos derivados) en su colección.

Anders Abel
fuente
2
+1, debe tenerse en cuenta que el patrón preferido es implementar la interfaz genérica e implementar explícitamente la interfaz no genérica. El rendimiento de rendimiento es la solución más natural para la interfaz genérica.
usuario7116
55
A menos que el OP vaya a realizar algún procesamiento en el enumerador, el uso del rendimiento devuelve solo la sobrecarga de otra máquina de estado. El OP solo debe devolver un enumerador proporcionado por una colección genérica subyacente.
Monroe Thomas
@MonroeThomas: Tienes razón. No leí la pregunta con mucho cuidado antes de escribir sobre yield return. Asumí erróneamente que había un procesamiento personalizado.
Anders Abel
4

Si trabaja con genéricos, use List en lugar de ArrayList. La lista tiene exactamente el método GetEnumerator que necesita.

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

convertir mi lista en una List<MyObject>, es una opción

ColWhi
fuente
0

Tenga en cuenta que el enfoque ya IEnumerable<T>implementado por System.Collectionslo que otro enfoque es derivar su MyObjectsclase System.Collectionscomo una clase base ( documentación ):

System.Collections: proporciona la clase base para una colección genérica.

Después podemos hacer nuestra propia implemenation para anular los virtuales System.Collectionsmétodos para proporcionar un comportamiento personalizado (sólo para ClearItems, InsertItem, RemoveItem, y SetItemjunto con el Equals, GetHashCodey ToStringdesde Object). A diferencia del List<T>que no está diseñado para ser fácilmente extensible.

Ejemplo:

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

Tenga en cuenta que conducir collectionsolo se recomienda en caso de que se necesite un comportamiento de recolección personalizado, lo que rara vez ocurre. ver uso .

Shahar Shokrani
fuente