Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Um iterador pode ser usado para percorrer coleções como listas e matrizes.
Um método iterador ou um acessador get
executa uma iteração personalizada em uma coleção. Um método iterador usa a instrução Yield para retornar cada elemento um de cada vez. Quando uma instrução Yield
for atingida, o local atual no código será lembrado. Na próxima vez que a função de iterador for chamada, a execução é reiniciada desse local.
Um iterador é consumido no código cliente, usando uma instrução For Each...Next ou usando uma consulta LINQ.
No exemplo a seguir, a primeira iteração do For Each
loop faz com que a execução prossiga no SomeNumbers
método iterador até que a primeira Yield
instrução seja atingida. Essa iteração retorna um valor de 3 e o local atual no método iterador é mantido. Na próxima iteração do loop, a execução no método iterador continua de onde parou, parando novamente quando atingir uma Yield
declaração. Essa iteração retorna um valor de 5 e a localização atual no método iterador é retida novamente. O loop é concluído quando o final do método iterador é atingido.
Sub Main()
For Each number As Integer In SomeNumbers()
Console.Write(number & " ")
Next
' Output: 3 5 8
Console.ReadKey()
End Sub
Private Iterator Function SomeNumbers() As System.Collections.IEnumerable
Yield 3
Yield 5
Yield 8
End Function
O tipo de retorno de um método iterador ou acessador get
pode ser IEnumerable, IEnumerable<T>, IEnumerator ou IEnumerator<T>.
Você pode usar uma declaração Exit Function
ou Return
para encerrar a iteração.
Uma função iteradora ou declaração de acessador get
do Visual Basic inclui um modificador do Iterador.
Os iteradores foram introduzidos no Visual Basic no Visual Studio 2012.
Observação
Para todos os exemplos no artigo, exceto o exemplo de Iterador Simples, inclua instruções Imports para os namespaces System.Collections
e System.Collections.Generic
.
Iterador simples
O exemplo a seguir contém uma única instrução Yield
que está dentro de um loop For...Next. Em Main
, cada iteração do corpo da instrução For Each
cria uma chamada para a função iteradora, que avança para a próxima instrução Yield
.
Sub Main()
For Each number As Integer In EvenSequence(5, 18)
Console.Write(number & " ")
Next
' Output: 6 8 10 12 14 16 18
Console.ReadKey()
End Sub
Private Iterator Function EvenSequence(
ByVal firstNumber As Integer, ByVal lastNumber As Integer) _
As System.Collections.Generic.IEnumerable(Of Integer)
' Yield even numbers in the range.
For number As Integer = firstNumber To lastNumber
If number Mod 2 = 0 Then
Yield number
End If
Next
End Function
Criando uma classe de coleção
No exemplo a seguir, a DaysOfTheWeek
classe implementa a IEnumerable interface, que requer um GetEnumerator método. O compilador chama implicitamente o GetEnumerator
método, que retorna um IEnumerator.
O método GetEnumerator
retorna cada string uma de cada vez usando a instrução Yield
, com um modificador Iterator
na declaração da função.
Sub Main()
Dim days As New DaysOfTheWeek()
For Each day As String In days
Console.Write(day & " ")
Next
' Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey()
End Sub
Private Class DaysOfTheWeek
Implements IEnumerable
Public days =
New String() {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
Public Iterator Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
' Yield each day of the week.
For i As Integer = 0 To days.Length - 1
Yield days(i)
Next
End Function
End Class
O exemplo a seguir cria uma Zoo
classe que contém uma coleção de animais.
A instrução For Each
que se refere à instância de classe theZoo
implicitamente chama o método GetEnumerator
. As instruções For Each
que se referem às propriedades Birds
e Mammals
utilizam o método iterador nomeado AnimalsForType
.
Sub Main()
Dim theZoo As New Zoo()
theZoo.AddMammal("Whale")
theZoo.AddMammal("Rhinoceros")
theZoo.AddBird("Penguin")
theZoo.AddBird("Warbler")
For Each name As String In theZoo
Console.Write(name & " ")
Next
Console.WriteLine()
' Output: Whale Rhinoceros Penguin Warbler
For Each name As String In theZoo.Birds
Console.Write(name & " ")
Next
Console.WriteLine()
' Output: Penguin Warbler
For Each name As String In theZoo.Mammals
Console.Write(name & " ")
Next
Console.WriteLine()
' Output: Whale Rhinoceros
Console.ReadKey()
End Sub
Public Class Zoo
Implements IEnumerable
' Private members.
Private animals As New List(Of Animal)
' Public methods.
Public Sub AddMammal(ByVal name As String)
animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Mammal})
End Sub
Public Sub AddBird(ByVal name As String)
animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Bird})
End Sub
Public Iterator Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
For Each theAnimal As Animal In animals
Yield theAnimal.Name
Next
End Function
' Public members.
Public ReadOnly Property Mammals As IEnumerable
Get
Return AnimalsForType(Animal.TypeEnum.Mammal)
End Get
End Property
Public ReadOnly Property Birds As IEnumerable
Get
Return AnimalsForType(Animal.TypeEnum.Bird)
End Get
End Property
' Private methods.
Private Iterator Function AnimalsForType( _
ByVal type As Animal.TypeEnum) As IEnumerable
For Each theAnimal As Animal In animals
If (theAnimal.Type = type) Then
Yield theAnimal.Name
End If
Next
End Function
' Private class.
Private Class Animal
Public Enum TypeEnum
Bird
Mammal
End Enum
Public Property Name As String
Public Property Type As TypeEnum
End Class
End Class
Blocos try
O Visual Basic permite uma instrução Yield
no bloco Try
de uma Instrução Try... Catch... Finally. Um bloco Try
que tem uma instrução Yield
pode ter blocos Catch
e pode ter um bloco Finally
.
O exemplo a seguir inclui Try
, Catch
e Finally
blocos em uma função de iterador. O bloco Finally
na função do iterador é executado antes da iteração For Each
ser concluída.
Sub Main()
For Each number As Integer In Test()
Console.WriteLine(number)
Next
Console.WriteLine("For Each is done.")
' Output:
' 3
' 4
' Something happened. Yields are done.
' Finally is called.
' For Each is done.
Console.ReadKey()
End Sub
Private Iterator Function Test() As IEnumerable(Of Integer)
Try
Yield 3
Yield 4
Throw New Exception("Something happened. Yields are done.")
Yield 5
Yield 6
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
Console.WriteLine("Finally is called.")
End Try
End Function
Uma Yield
instrução não pode estar dentro de um bloco Catch
ou de um bloco Finally
.
Se o For Each
corpo (em vez do método iterador) gerar uma exceção, um Catch
bloco na função iterador não será executado, mas um Finally
bloco na função iterador será executado. Um Catch
bloco dentro de uma função de iterador captura apenas exceções que ocorrem dentro da função iterador.
Métodos anônimos
No Visual Basic, uma função anônima pode ser uma função de iterador. O exemplo a seguir ilustra essa situação.
Dim iterateSequence = Iterator Function() _
As IEnumerable(Of Integer)
Yield 1
Yield 2
End Function
For Each number As Integer In iterateSequence()
Console.Write(number & " ")
Next
' Output: 1 2
Console.ReadKey()
O exemplo a seguir tem um método não iterador que valida os argumentos. O método retorna o resultado de um iterador anônimo que descreve os elementos da coleção.
Sub Main()
For Each number As Integer In GetSequence(5, 10)
Console.Write(number & " ")
Next
' Output: 5 6 7 8 9 10
Console.ReadKey()
End Sub
Public Function GetSequence(ByVal low As Integer, ByVal high As Integer) _
As IEnumerable
' Validate the arguments.
If low < 1 Then
Throw New ArgumentException("low is too low")
End If
If high > 140 Then
Throw New ArgumentException("high is too high")
End If
' Return an anonymous iterator function.
Dim iterateSequence = Iterator Function() As IEnumerable
For index = low To high
Yield index
Next
End Function
Return iterateSequence()
End Function
Se a validação estiver dentro da função do iterador, a validação não poderá ser executada até o início da primeira iteração do corpo For Each
.
Usando iteradores com uma lista genérica
No exemplo a seguir, a Stack(Of T)
classe genérica implementa a IEnumerable<T> interface genérica. O Push
método atribui valores a uma matriz do tipo T
. O GetEnumerator método retorna os valores da matriz usando a Yield
instrução.
Além do método genérico GetEnumerator , o método não genérico GetEnumerator também deve ser implementado. Isso ocorre porque IEnumerable<T> herda de IEnumerable. A implementação não genérica adia a implementação genérica.
O exemplo usa iteradores nomeados para dar suporte a várias maneiras de iterar na mesma coleção de dados. Esses iteradores nomeados são as propriedades TopToBottom
e BottomToTop
, e o método TopN
.
A BottomToTop
declaração de propriedade inclui a Iterator
palavra-chave.
Sub Main()
Dim theStack As New Stack(Of Integer)
' Add items to the stack.
For number As Integer = 0 To 9
theStack.Push(number)
Next
' Retrieve items from the stack.
' For Each is allowed because theStack implements
' IEnumerable(Of Integer).
For Each number As Integer In theStack
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 9 8 7 6 5 4 3 2 1 0
' For Each is allowed, because theStack.TopToBottom
' returns IEnumerable(Of Integer).
For Each number As Integer In theStack.TopToBottom
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 9 8 7 6 5 4 3 2 1 0
For Each number As Integer In theStack.BottomToTop
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 0 1 2 3 4 5 6 7 8 9
For Each number As Integer In theStack.TopN(7)
Console.Write("{0} ", number)
Next
Console.WriteLine()
' Output: 9 8 7 6 5 4 3
Console.ReadKey()
End Sub
Public Class Stack(Of T)
Implements IEnumerable(Of T)
Private values As T() = New T(99) {}
Private top As Integer = 0
Public Sub Push(ByVal t As T)
values(top) = t
top = top + 1
End Sub
Public Function Pop() As T
top = top - 1
Return values(top)
End Function
' This function implements the GetEnumerator method. It allows
' an instance of the class to be used in a For Each statement.
Public Iterator Function GetEnumerator() As IEnumerator(Of T) _
Implements IEnumerable(Of T).GetEnumerator
For index As Integer = top - 1 To 0 Step -1
Yield values(index)
Next
End Function
Public Iterator Function GetEnumerator1() As IEnumerator _
Implements IEnumerable.GetEnumerator
Yield GetEnumerator()
End Function
Public ReadOnly Property TopToBottom() As IEnumerable(Of T)
Get
Return Me
End Get
End Property
Public ReadOnly Iterator Property BottomToTop As IEnumerable(Of T)
Get
For index As Integer = 0 To top - 1
Yield values(index)
Next
End Get
End Property
Public Iterator Function TopN(ByVal itemsFromTop As Integer) _
As IEnumerable(Of T)
' Return less than itemsFromTop if necessary.
Dim startIndex As Integer =
If(itemsFromTop >= top, 0, top - itemsFromTop)
For index As Integer = top - 1 To startIndex Step -1
Yield values(index)
Next
End Function
End Class
Informações de sintaxe
Um iterador pode ocorrer como um método ou como um acessador get
. Um iterador não pode ocorrer em um evento, um construtor de instância, um construtor estático ou um destruidor estático.
Uma conversão implícita deve existir do tipo de expressão na instrução Yield
para o tipo de retorno do iterador.
No Visual Basic, um método iterador não pode ter parâmetros ByRef
.
No Visual Basic, "Yield" não é uma palavra reservada e tem um significado especial somente quando é usado em um método Iterator
ou acessador get
.
Implementação técnica
Embora você escreva um iterador como um método, o compilador o converte em uma classe aninhada que é, de fato, uma máquina de estado. Essa classe mantém o controle da posição do iterador enquanto o loop For Each...Next
no código cliente continuar.
Para ver o que o compilador faz, você pode usar a ferramenta Ildasm.exe para exibir o código de linguagem intermediária comum gerado para um método iterador.
Ao criar um iterador para uma classe ou struct, você não precisa implementar toda IEnumerator a interface. Quando o compilador detecta o iterador, ele gera automaticamente os métodos Current
, MoveNext
e Dispose
da interface IEnumerator ou IEnumerator<T>.
A cada iteração sucessiva do loop For Each…Next
(ou a chamada direta ao IEnumerator.MoveNext
), o próximo corpo de código do iterador continua, depois da instrução Yield
anterior. Em seguida, ele continuará até a próxima instrução Yield
, até que o final do corpo do iterador seja alcançado ou até que uma instrução Exit Function
ou Return
seja encontrada.
Os iteradores não dão suporte ao IEnumerator.Reset método. Para iterar novamente desde o início, você deve obter um novo iterador.
Para obter informações adicionais, consulte a Especificação de Linguagem do Visual Basic.
Uso de Iteradores
Os iteradores permitem manter a simplicidade de um For Each
loop quando você precisa usar código complexo para preencher uma sequência de lista. Isso pode ser útil quando você deseja fazer o seguinte:
Modificar a sequência de lista após a primeira iteração de loop
For Each
.Evite carregar totalmente uma lista grande antes da primeira iteração de um
For Each
loop. Um exemplo é uma busca paginada para carregar um lote de linhas da tabela. Outro exemplo é o EnumerateFiles método, que implementa iteradores dentro do .NET Framework.Encapsular a criação da lista no iterador. No método iterador, você pode criar a lista e, em seguida, produzir cada resultado em um loop.