Como faço para implementar IEnumerable <T>

124

Eu sei como implementar o IEnumerable não genérico, assim:

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

No entanto, também notei que o IEnumerable tem uma versão genérica IEnumerable<T>, mas não consigo descobrir como implementá-lo.

Se eu adicionar using System.Collections.Generic;ao meu uso diretivas, e depois alterar:

class MyObjects : IEnumerable

para:

class MyObjects : IEnumerable<MyObject>

E, em seguida, clique com o botão direito do mouse IEnumerable<MyObject>e selecione Implement Interface => Implement Interface, o Visual Studio adiciona o seguinte bloco de código:

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

Retornar o objeto IEnumerable não genérico do GetEnumerator();método não funciona desta vez, então o que devo colocar aqui? A CLI agora ignora a implementação não genérica e segue diretamente para a versão genérica quando tenta enumerar minha matriz durante o loop foreach.

JMK
fonte

Respostas:

149

Se você optar por usar uma coleção genérica, como em List<MyObject>vez de ArrayList, verá que List<MyObject>ele fornecerá enumeradores genéricos e não genéricos que você pode 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
fonte
1
Na implementação não genérica, existe uma diferença entre retornar this.GetEnumerator()e simplesmente retornar GetEnumerator()?
quer
1
@TannerSwett Não há diferença.
Monroe Thomas
Você também pode herdar Collection<MyObject>e não precisará escrever nenhum código personalizado.
31520 John Johniou
@ ja72 E se você já estiver herdando de outra classe base e não puder herdar da Collection <MyObject>?
9138 Monroe Thomas
1
@ MonroeThomas - é necessário agrupar List<T>e implementar IEnumerable<T>como mostrado na sua resposta.
21418 John Johniou
69

Você provavelmente não deseja uma implementação explícita de IEnumerable<T>(que é o que você mostrou).

O padrão usual é usar IEnumerable<T>'s' GetEnumeratorna implementação 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();
    }
}
user7116
fonte
2
Esta é uma boa solução se SomeCollection <T> ainda não implementar IEnumerable <T> ou se você precisar transformar ou executar outro processamento em cada elemento antes de retornar na sequência de enumeração. Se nenhuma dessas condições for verdadeira, provavelmente será mais eficiente retornar this.foos.GetEnumerator ();
22612 Monroe Thomas
@MonroeThomas: concordou. Nenhuma pista de qual coleção o OP está usando.
user7116
8
Bom ponto. Mas a implementação IEnumerableé redundante ( IEnumerable<T>já herda dela).
rsenna
@rsenna Isso é verdade, no entanto, lembre-se de que mesmo interfaces .NET como IList implementam interfaces de forma redundante, é para facilitar a leitura - interface pública IList: ICollection, IEnumerable
David Klempfner
@ Backwards_Dave Na verdade (ainda) acho que fica menos legível, pois adiciona ruído desnecessário, mas entendo o que você disse e acho que é uma opinião válida.
rsenna
24

Por que você faz isso manualmente? yield returnautomatiza todo o processo de manipulação de iteradores. (Eu também escrevi sobre isso no meu blog , incluindo uma olhada no código gerado pelo compilador).

Se você realmente deseja fazer isso sozinho, também precisará retornar um enumerador genérico. Você não poderá mais usar um, ArrayListuma vez que não é genérico. Altere para um em List<MyObject>vez disso. Obviamente, isso pressupõe que você tenha apenas objetos do tipo MyObject(ou tipos derivados) em sua coleção.

Anders Abel
fonte
2
+1, note que o padrão preferido é implementar a interface genérica e implementar explicitamente a interface não genérica. O retorno do rendimento é a solução mais natural para a interface genérica.
user7116
5
A menos que o OP faça algum processamento no enumerador, o uso de yield return apenas adiciona a sobrecarga de outra máquina de estado. O OP deve retornar um enumerador fornecido por uma coleção genérica subjacente.
22612 Monroe Thomas
@ MonroeThomas: Você está certo. Não li a pergunta com muito cuidado antes de escrever sobre yield return. Eu assumi erroneamente que havia algum processamento personalizado.
Anders Abel
4

Se você trabalha com genéricos, use Lista em vez de ArrayList. A lista possui exatamente o método GetEnumerator que você precisa.

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

transformar minha lista em uma List<MyObject>é uma opção

ColWhi
fonte
0

Observe que o IEnumerable<T>método já implementado pela System.Collectionsoutra abordagem é derivar sua MyObjectsclasse System.Collectionscomo uma classe base ( documentação ):

System.Collections: fornece a classe base para uma coleção genérica.

Mais tarde, pode fazer nossa própria implemenation para substituir as virtuais System.Collectionsmétodos para fornecer o comportamento personalizado (apenas para ClearItems, InsertItem, RemoveIteme, SetItemjuntamente com Equals, GetHashCodee, ToStringa partir Object). Ao contrário do List<T>que não foi projetado para ser facilmente extensível.

Exemplo:

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

Observe que a orientação é collectionapenas recomendada caso seja necessário um comportamento de coleção personalizado, o que raramente acontece. veja o uso .

Shahar Shokrani
fonte