Instrução For Each...Next (Visual Basic)

Repete um grupo de instruções para cada elemento em uma coleção.

Sintaxe

For Each element [ As datatype ] In group
    [ statements ]
    [ Continue For ]
    [ statements ]
    [ Exit For ]
    [ statements ]
Next [ element ]

Partes

Termo Definição
element Obrigatório na instrução For Each. Opcional na instrução Next. Variável. Usada para iterar por meio dos elementos da coleção.
datatype Opcional se Option Infer estiver ativado (o padrão) ou element já estiver declarado; obrigatório se Option Infer estiver desativado e element ainda não estiver declarado. O tipo de dados de element.
group Obrigatórios. Uma variável com um tipo que é um tipo de coleção ou Objeto. Refere-se à coleção sobre a qual as statements devem ser repetidas.
statements Opcional. Uma ou mais instruções entre For Each e Next que são executadas em cada item em group.
Continue For Opcional. Transfere o controle para o início do loop For Each.
Exit For Opcional. Transfere o controle para fora do loop For Each.
Next Obrigatórios. Finaliza a definição do loop For Each.

Exemplo simples

Use um loop For Each...Next quando quiser repetir um conjunto de instruções para cada elemento de uma coleção ou matriz.

Dica

A Instrução For...Next funciona bem quando você pode associar cada iteração de um loop a uma variável de controle e determinar os valores iniciais e finais dessa variável. No entanto, quando você está lidando com uma coleção, o conceito de valores iniciais e finais não é significativo e você não sabe necessariamente quantos elementos a coleção tem. Nesse tipo de caso, um loop For Each...Next muitas vezes é uma escolha melhor.

No exemplo a seguir, a instrução For EachNext itera por meio de todos os elementos de uma coleção List.

' Create a list of strings by using a
' collection initializer.
Dim lst As New List(Of String) _
    From {"abc", "def", "ghi"}

' Iterate through the list.
For Each item As String In lst
    Debug.Write(item & " ")
Next
Debug.WriteLine("")
'Output: abc def ghi

Para obter mais exemplos, confira Coleções e Matrizes.

Nested Loops

Você pode aninhar loops For Each colocando um loop dentro de outro.

O exemplo a seguir demonstra estruturas For EachNext aninhadas.

' Create lists of numbers and letters
' by using array initializers.
Dim numbers() As Integer = {1, 4, 7}
Dim letters() As String = {"a", "b", "c"}

' Iterate through the list by using nested loops.
For Each number As Integer In numbers
    For Each letter As String In letters
        Debug.Write(number.ToString & letter & " ")
    Next
Next
Debug.WriteLine("")
'Output: 1a 1b 1c 4a 4b 4c 7a 7b 7c

Quando você aninha loops, cada loop deve ter uma variável element exclusiva.

Você também pode aninhar diferentes tipos de estruturas de controle entre si. Para obter mais informações, confira Estruturas de controle aninhadas.

Exit For e Continue For

A instrução Exit For faz com que a execução saia do loop For...Next e transfere o controle para a instrução que segue a instrução Next.

A instruçãoContinue For transfere o controle imediatamente para a próxima iteração do loop. Para obter mais informações, confira Instrução Continue.

O exemplo a seguir mostra como usar as instruções Continue For e Exit For.

Dim numberSeq() As Integer =
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

For Each number As Integer In numberSeq
    ' If number is between 5 and 8, continue
    ' with the next iteration.
    If number >= 5 And number <= 8 Then
        Continue For
    End If

    ' Display the number.
    Debug.Write(number.ToString & " ")

    ' If number is 10, exit the loop.
    If number = 10 Then
        Exit For
    End If
Next
Debug.WriteLine("")
' Output: 1 2 3 4 9 10

Você pode colocar qualquer número de instruções Exit For em um loop For Each. Quando usado em loops For Each aninhados, Exit For faz com que a execução saia do loop mais interno e transfere o controle para o próximo nível mais alto de aninhamento.

Exit For geralmente é usado após uma avaliação de alguma condição, por exemplo, em uma estrutura If...Then...Else. Talvez você queira usar Exit For para as seguintes condições:

  • Continuar a iterar é desnecessário ou impossível. Isso pode ser causado por um valor incorreto ou uma solicitação de encerramento.

  • Uma exceção é capturada em um Try...Catch...Finally. Você pode usar Exit For no final do bloco Finally.

  • Há um loop infinito, que é um loop que pode se executado várias vezes ou até infinitamente. Se você detectar essa condição, poderá usar Exit For para pular o loop. Para obter mais informações, confira Instrução Do...Loop.

Iterators

Use um iterador para realizar uma iteração personalizada em uma coleção. Um iterador pode ser uma função ou um acessador Get. Ele usa uma instrução Yield para retornar um elemento da coleção por vez.

Chame um iterador usando uma instrução For Each...Next. Cada iteração do loop For Each chama o iterador. Quando uma instrução Yield é alcançada no iterador, a expressão na instrução Yield é retornada e o local atual no código é retido. A execução será reiniciada desse local na próxima vez que o iterador for chamado.

O exemplo a seguir usa uma função iteradora. A função iteradora tem uma instrução Yield que está em um loop For…Next. No método ListEvenNumbers, cada iteração do corpo da instrução For Each cria uma chamada à função iteradora, que avança para a próxima instrução Yield.

Public Sub ListEvenNumbers()
    For Each number As Integer In EvenSequence(5, 18)
        Debug.Write(number & " ")
    Next
    Debug.WriteLine("")
    ' Output: 6 8 10 12 14 16 18
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 = firstNumber To lastNumber
        If number Mod 2 = 0 Then
            Yield number
        End If
    Next
End Function

Para obter mais informações, confira Iteradores, Instrução Yield e Iterador.

Implementação Técnica

Quando uma instrução For EachNext é executada, o Visual Basic avalia a coleção apenas uma vez, antes do loop ser iniciado. Se o bloco de instruções alterar element ou group, essas alterações não afetarão a iteração do loop.

Quando todos os elementos na coleção foram atribuídos a elementsucessivamente, o loop For Each para e o controle passa para a instrução após a instrução Next.

Se Option Infer estiver ativado (configuração padrão), o compilador do Visual Basic poderá inferir o tipo de dados de element. Se ele estiver desativado e element não tiver sido declarado fora do loop, você deverá declará-lo na instrução For Each. Para declarar o tipo de dados de element explicitamente, use uma cláusula As. A menos que o tipo de dados do elemento seja definido fora do constructo For Each...Next, o escopo é o corpo do loop. Observe que você não pode declarar element fora e dentro do loop.

Como opção, você pode especificar element na instrução Next. Isso melhora a legibilidade do programa, especialmente se você tiver loops For Each aninhados. Você precisa especificar a mesma variável que a exibida na instrução For Each correspondente.

Talvez você queira evitar alterar o valor de element dentro de um loop. Fazer isso pode dificultar a leitura e a depuração do código. Alterar o valor de group não afeta a coleção nem os elementos dela, que foram determinados quando o loop foi inserido pela primeira vez.

Quando você está aninhando loops, se uma instrução Next de um nível de aninhamento externo for encontrada antes do Next de nível interno, o compilador sinalizará um erro. No entanto, o compilador só poderá detectar esse erro sobreposto se você especificar element em cada instrução Next.

Se o código depender de percorrer uma coleção em uma determinada ordem, um loop For Each...Next não será a melhor opção, a menos que você saiba as características do objeto enumerador que a coleção expõe. A ordem de passagem não é determinada pelo Visual Basic, mas pelo método MoveNext do objeto enumerador. Portanto, talvez não seja possível prever qual elemento da coleção é o primeiro a ser retornado em element ou qual é o próximo a ser retornado após determinado elemento. Você pode obter resultados mais confiáveis usando uma estrutura de loop diferente, como For...Next ou Do...Loop.

O runtime deve ser capaz de converter os elementos em group em element. A instrução [Option Strict] controla se as conversões de expansão e restrição são permitidas (Option Strict está desativada, valor padrão) ou se somente conversões de expansão são permitidas (Option Strict está ativada). Para obter mais informações, confira Conversões de restrição.

O tipo de dados de group deve ser um tipo de referência que se refere a uma coleção ou a uma matriz enumerável. Mais comumente isso significa que group se refere a um objeto que implementa a interface IEnumerable do namespace System.Collections ou a interface IEnumerable<T> do namespace System.Collections.Generic. System.Collections.IEnumerable define o método GetEnumerator, que retorna um objeto enumerador para a coleção. O objeto enumerador implementa a interface System.Collections.IEnumerator do namespace System.Collections e expõe a propriedade Current e os métodos Reset e MoveNext. O Visual Basic os usa para percorrer a coleção.

Conversões de redução

Quando Option Strict é definido como On, as conversões de restrição normalmente causam erros do compilador. No entanto, em uma instrução For Each, as conversões dos elementos em group em element são avaliadas e executadas em tempo de execução, e os erros do compilador causados pelas conversões de restrição são suprimidos.

No exemplo a seguir, a atribuição de m como o valor inicial para n não é compilada quando Option Strict está ativada porque a conversão de um Long em um Integer é uma conversão de restrição. No entanto, na instrução For Each, nenhum erro do compilador é relatado, embora a atribuição para number exigir a mesma conversão de Long em Integer. Na instrução For Each que contém um número grande, ocorre um erro em tempo de execução quando ToInteger é aplicado ao número grande.

Option Strict On

Imports System

Module Program
    Sub Main(args As String())
        ' The assignment of m to n causes a compiler error when 
        ' Option Strict is on.
        Dim m As Long = 987
        'Dim n As Integer = m

        ' The For Each loop requires the same conversion but
        ' causes no errors, even when Option Strict is on.
        For Each number As Integer In New Long() {45, 3, 987}
            Console.Write(number & " ")
        Next
        Console.WriteLine()
        ' Output: 45 3 987

        ' Here a run-time error is raised because 9876543210
        ' is too large for type Integer.
        'For Each number As Integer In New Long() {45, 3, 9876543210}
        '    Console.Write(number & " ")
        'Next
    End Sub
End Module

Chamadas IEnumerator

Quando a execução de um loop For Each...Next é iniciada, o Visual Basic verifica se group se refere a um objeto de coleção válido. Se não for, ele vai gerar uma exceção. Caso contrário, ele chama o método MoveNext e a propriedade Current do objeto enumerador para retornar o primeiro elemento. Se MoveNext indicar que não há nenhum próximo elemento, ou seja, se a coleção estiver vazia, o loop For Each será interrompido e o controle passará para a instrução após a instrução Next. Caso contrário, o Visual Basic define element como o primeiro elemento e executa o bloco de instruções.

Sempre que o Visual Basic encontra a instrução Next, ela retorna à instrução For Each. Novamente, ele chama MoveNext e Current para retornar o próximo elemento e, novamente, executa o bloco ou interrompe o loop dependendo do resultado. Esse processo continua até MoveNext indicar que não há nenhum próximo elemento ou uma instrução Exit For encontrada.

Modificando a coleção. O objeto enumerador retornado por GetEnumerator normalmente não permite alterar a coleção adicionando, excluindo, substituindo ou reordenando elementos. Se você alterar a coleção depois de iniciar um loop For Each...Next, o objeto enumerador se tornará inválido e a próxima tentativa de acessar um elemento causará uma exceção InvalidOperationException.

No entanto, esse bloqueio de modificação não é determinado pelo Visual Basic, mas sim pela implementação da interface IEnumerable. É possível implementar IEnumerable de uma forma que permita a modificação durante a iteração. Se você estiver considerando fazer essa modificação dinâmica, certifique-se de entender as características da implementação IEnumerable na coleção que você está usando.

Modificando elementos da coleção. A propriedade Current do objeto enumerador é ReadOnly e retorna uma cópia local de cada elemento da coleção. Isso significa que você não pode modificar os próprios elementos em um loop For Each...Next. Qualquer modificação feita afeta apenas a cópia local de Current e não é refletida novamente na coleção subjacente. No entanto, se um elemento for um tipo de referência, você poderá modificar os membros da instância para a qual ele aponta. O exemplo a seguir modifica o membro BackColor de cada elemento thisControl. No entanto, você não pode modificar thisControl em si.

Sub LightBlueBackground(thisForm As System.Windows.Forms.Form)
    For Each thisControl In thisForm.Controls
        thisControl.BackColor = System.Drawing.Color.LightBlue
    Next thisControl
End Sub

O exemplo anterior pode modificar o membro BackColor de cada elemento thisControl, embora ele não possa modificar thisControl em si.

Percorrendo matrizes. Como a classe Array implementa a interface IEnumerable, todas as matrizes expõem o método GetEnumerator. Isso significa que você pode iterar por meio de uma matriz com um loop For Each...Next. No entanto, você só pode ler os elementos da matriz. Não é possível alterá-los.

Exemplo 1

O exemplo a seguir lista todas as pastas no C:\ diretório usando a classe DirectoryInfo.

Dim dInfo As New System.IO.DirectoryInfo("c:\")
For Each dir As System.IO.DirectoryInfo In dInfo.GetDirectories()
    Debug.WriteLine(dir.Name)
Next

Exemplo 2

O exemplo a seguir ilustra um procedimento para a classificação de uma coleção. O exemplo classifica instâncias de uma classe Car que estão armazenados em uma List<T>. A classe Car implementa a interface IComparable<T>, que requer que o método CompareTo seja implementado.

Cada chamada ao método CompareTo faz uma comparação única que é usada para classificação. Os códigos escritos pelo usuário no método CompareTo retornam um valor para cada comparação do objeto atual com outro objeto. O valor retornado será menor que zero se o objeto atual for menor que o outro objeto, maior que zero se o objeto atual for maior que o outro objeto e zero, se eles forem iguais. Isso permite que você defina no código os critérios para maior que, menor que e igual.

No método ListCars, a instrução cars.Sort() classifica a lista. Essa chamada para o método Sort da List<T> faz com que o método CompareTo seja chamado automaticamente para os objetos Car na List.

Public Sub ListCars()

    ' Create some new cars.
    Dim cars As New List(Of Car) From
    {
        New Car With {.Name = "car1", .Color = "blue", .Speed = 20},
        New Car With {.Name = "car2", .Color = "red", .Speed = 50},
        New Car With {.Name = "car3", .Color = "green", .Speed = 10},
        New Car With {.Name = "car4", .Color = "blue", .Speed = 50},
        New Car With {.Name = "car5", .Color = "blue", .Speed = 30},
        New Car With {.Name = "car6", .Color = "red", .Speed = 60},
        New Car With {.Name = "car7", .Color = "green", .Speed = 50}
    }

    ' Sort the cars by color alphabetically, and then by speed
    ' in descending order.
    cars.Sort()

    ' View all of the cars.
    For Each thisCar As Car In cars
        Debug.Write(thisCar.Color.PadRight(5) & " ")
        Debug.Write(thisCar.Speed.ToString & " ")
        Debug.Write(thisCar.Name)
        Debug.WriteLine("")
    Next

    ' Output:
    '  blue  50 car4
    '  blue  30 car5
    '  blue  20 car1
    '  green 50 car7
    '  green 10 car3
    '  red   60 car6
    '  red   50 car2
End Sub

Public Class Car
    Implements IComparable(Of Car)

    Public Property Name As String
    Public Property Speed As Integer
    Public Property Color As String

    Public Function CompareTo(ByVal other As Car) As Integer _
        Implements System.IComparable(Of Car).CompareTo
        ' A call to this method makes a single comparison that is
        ' used for sorting.

        ' Determine the relative order of the objects being compared.
        ' Sort by color alphabetically, and then by speed in
        ' descending order.

        ' Compare the colors.
        Dim compare As Integer
        compare = String.Compare(Me.Color, other.Color, True)

        ' If the colors are the same, compare the speeds.
        If compare = 0 Then
            compare = Me.Speed.CompareTo(other.Speed)

            ' Use descending order for speed.
            compare = -compare
        End If

        Return compare
    End Function
End Class

Confira também