.NET - As interface IEnumerable e IEnumerator


A plataforma .NET tem evoluído continuamente fornecendo recursos cada vez mais avançados para o tratamento de coleções.

Para podermos tratar uma coleção de informações temos que escrever e executar consultas e realizar iterações sobre o resultado.

Quando o assunto é realizar iterações sobre coleções devemos conhecer bem duas interfaces: IEnumerable e IEnumerator.

A interface IEnumerable suporta uma iteração sobre uma coleção não genérica. A interface possui apenas o método GetEnumerator que retorna um enumerador que itera sobre a coleção.

A interface IEnumerator suporta uma iteração simples sobre uma coleção sendo a interface base para todos os enumeradores.

As interfaces IEnumerable e IEnumerator estão no namespace System.Collections.

A hierarquia de interface deste namespace pode ser visto na figura abaixo:

IEnumerable - É a base direta ou indiretamente para algumas outras Interfaces e todas as coleções a implementam. Esta interface tem apenas um método a ser implementado, chamado GetEnumerator, qual retorna um objeto do tipo da Interface de IEnumerator.

IEnumerator - É a base para todos os enumeradores. Composta por uma propriedade Current e dois métodos moveNext e Reset), percorre e lê todos os elementos de uma determinada coleção, permitindo apenas ler os dados, não podendo alterá-los. Vale levar em consideração que enumeradores não podem ser utilizados para a coleção subjacente.

ICollection - É a Interface base para todas as coleções que estão contidas dentro do namespace System.Collections. Direta ou indiretamente essas classes a implementa. Ela por sua vez é composta por três propriedades Count, IsSynchronized e SyncRoot e um método CopyTo.

 

A interface IEnumerator possui os seguintes membros:

Abaixo uma figura que mostra como funcionam os membros do IEnumerator:(fonte: http://www.codeproject.com/KB/cs/sssienumerable.aspx)

Vejamos o funcionamentos destes membros:

Um enumerador permanece válido, desde que a coleção permaneça inalterada. Se forem feitas alterações na coleção, como adicionar, modificar ou excluir elementos, o enumerador ficará invalidado e a próxima chamada para MoveNext ou Reset gera uma exceção InvalidOperationException. Se a coleção é modificado entre MoveNext e Current, Current retornará o elemento que é definido, mesmo se o enumerador está já invalidado.

A instrução foreach (linguagem C#)/for each (Visual Basic) oculta a complexidade dos enumeradores. Portanto é recomendável usar foreach/for each ao invés de tentar manipular diretamente o enumerador. (Os enumeradores podem ser usados para ler dados em um coleção mas não podem modificar estes dados.)

Implementando as interface IEnumerable e IEnumerator

Crie um novo projeto usando o Visual Basic 2010 Express Edition do tipo Console Application com o nome IEnumerable_Exemplo1:

    Public Class TimesEnumerator
        Implements IEnumerator

        Private times() As String = {"Santos", "São Paulo", "Palmeiras", "Corintians", "Flamengo", "Vasco"}
     
        Private posicao As Integer = -1

        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                If Me.posicao < 0 OrElse Me.posicao > 4 Then
                    Throw New InvalidOperationException
                Else
                    Return Me.times(Me.posicao)
                End If
            End Get
        End Property

        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            Me.posicao += 1
            Return Me.posicao <= 4
        End Function
        Public Sub Reset() Implements IEnumerator.Reset
            Me.posicao = -1
        End Sub
    End Class

No código acima criamos a classe TimesEnumerator e implementamos a interface IEnumerator; dessa forma tivemos que definir os métodos: MoveNext() e Reset() e a propriedade Current.

Se você quiser exibir os valores do array de strings times() terá que usar um laço for/next e verificar se o método retorna True ou False para em seguida usar a propriedade Current para recuperar o item da coleção.

Uma maneira mais simples seria usar um laço For/Each mas para isso teremos que criar uma classe auxiliar implementando a interface IEnumerable que possui o método GetEnumerator que retornará uma instância da classe TimesEnumerator.

    Public Class Times
        Implements IEnumerable

        Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
            Return New TimesEnumerator()
        End Function
    End Class

Agora já podemos usar o laço For/Each para iterar sobre a coleção:

For Each time As String In New Times()
    Console.WriteLine(time)
Next

Executando o projeto teremos o seguinte resultado:

Vamos então criar outro exemplo mais completo usando ambas a interfaces e o laço For/Each.

Vejamos um exemplo simples de implementação das interfaces IEnumerable e IEnumerator para uma coleção customizada. No exemplo os membros destas interfaces foram implementados para realizar a iteração sobre uma coleção usando for each.

Crie um novo projeto usando o Visual Basic 2010 Express Edition do tipo Console Application com o nome IEnumerable_Exemplo2:


Imports System.Collections

Module Module1

    Sub Main()

        Dim pessoasArray() As Pessoa = { _
            New Pessoa("Macoratti", "macoratti@yahoo.com"), _
            New Pessoa("Miriam", "miriam@hotmail.com"), _
            New Pessoa("Jefferson", "jeff@bol.com.br")}

        Dim pessoasNaLista As New Pessoas(pessoasArray)
        Dim p As Pessoa
        For Each p In pessoasNaLista
            Console.WriteLine(p.Nome + " " + p.Email)
        Next
        Console.ReadKey()
    End Sub

    Public Class Pessoa
        Public Sub New(ByVal _nome As String, ByVal _email As String)
            Me.Nome = _nome
            Me.Email = _email
        End Sub

        Public Nome As String
        Public Email As String
    End Class

    Public Class Pessoas
        Implements IEnumerable

        Private _pessoas() As Pessoa

        Public Sub New(ByVal pArray() As Pessoa)
            _pessoas = New Pessoa(pArray.Length - 1) {}

            Dim i As Integer
            For i = 0 To pArray.Length - 1
                _pessoas(i) = pArray(i)
            Next i
        End Sub

        Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
            Return New PessoasEnum(_pessoas)
        End Function

    End Class

    Public Class PessoasEnum
        Implements IEnumerator

        Public _pessoas() As Pessoa

        ' Enumeradores são posicionados antes do primeiro elemento
        ' ate que o m´metodo MoveNext() seja chamado
        Dim posicao As Integer = -1
        Public Sub New(ByVal lista() As Pessoa)
            _pessoas = lista
        End Sub
        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            posicao = posicao + 1
            Return (posicao < _pessoas.Length)
        End Function
        Public Sub Reset() Implements IEnumerator.Reset
            posicao = -1
        End Sub
        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                Try
                    Return _pessoas(posicao)
                Catch ex As IndexOutOfRangeException
                    Throw New InvalidOperationException()
                End Try
            End Get
        End Property
    End Class
End Module
Criamos a classe Pessoa contendo as propriedades :
  • Nome
  • Email

Criamos a classe Pessoas que implementa a interface IEnumerable.

Nesta classe definimos o método GetEnumerator()

Definimos a classe PessoaEnum que implementa a interface IEnumerator

Nesta classe definimos os métodos:

  • MoveNext
  • Reset

e a propriedade

  • Current

No método Main() instanciamos a classe Pessoa e criamos alguns objetos do
tipo Pessoa;

Criamos uma instância da classe Pessoas passando o array de pessoas.

A seguir percorremos a coleção usando um For Each.

Executando o projeto teremos o seguinte resultado:

Se você conhece a sintaxe da linguagem Visual Basic deve lembrar que para percorrer uma coleção fazíamos assim:

For i = 1 to fimdaColeção
 
 
   ' Fazer alguma coisa com a Coleção(i)
Next i

No Visual Basic .NET temos a sintaxe:

For Each elemento in Coleção
   
  
' Fazer alguma coisa com o elemento
Next element

Uma das razões pela qual a sintaxe VB .NET - For Each - é melhor é que você não tem que saber onde a coleção começa e termina. O VB .NET faz isso para você.

Pegue o projeto completo aqui:   IEnumerable_Exemplo.zip

"Eu sou a luz que vim ao mundo, para que todo aquele que crê em mim não permaneça nas trevas." João 12:46

Referências:


José Carlos Macoratti