对于每个...Next 语句 (Visual Basic)

对集合中的每个元素重复一组语句。

语法

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

部件

术语 定义
element For Each 语句中是必需的。 语句中 Next 可选。 变量。 用于循环访问集合的元素。
datatype 可选(如果 Option Infer 处于打开状态)或 element 已声明;如果 Option Infer 已关闭且 element 尚未声明,则为必需。 的 element数据类型。
group 必填。 具有集合类型或对象类型的变量。 指要重复的 statements 集合。
statements 可选。 在每个项group上运行的For EachNext个或多个语句。
Continue For 可选。 将控制权传输到循环的 For Each 开始。
Exit For 可选。 将控制从 For Each 循环转移出去。
Next 必填。 终止For Each循环的定义。

简单示例

For Each如果要对集合或数组的每个元素重复一组语句,请使用 ...Next 循环。

小窍门

A For...下一个语句 非常适合将循环的每个迭代与控件变量相关联,并确定该变量的初始值和最终值。 但是,在处理集合时,初始值和最终值的概念没有意义,并且不一定知道集合具有多少个元素。 在这种情况下,For EachNext...循环通常是更好的选择。

在以下示例中 For Each,... Next 语句循环访问 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

有关更多示例,请参阅 集合数组

嵌套循环

可以通过将一个循环置于另一个循环中来嵌套 For Each 循环。

以下示例演示嵌套 For Each... Next 结构的成员。

' 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

嵌套循环时,每个循环必须具有唯 element 一变量。

还可以相互嵌套不同类型的控件结构。 有关详细信息,请参阅 嵌套控件结构

退出并继续

Exit For 语句导致执行退出 ...For Next 循环并立即将控制转移到 Next 语句后面的语句。

Continue For 语句将控制权立即传输到循环的下一次迭代。 有关详细信息,请参阅 Continue 语句

以下示例演示如何使用 Continue For and 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

可以将任意数量的 Exit For 语句放入循环 For Each 中。 在嵌套 For Each 循环中使用时, Exit For 会导致执行退出最内部的循环,并将控制权传输到下一个更高的嵌套级别。

Exit For通常用于评估某些条件后,例如,在 ...ThenIf...Else结构。 你可能想要在以下情况中使用Exit For

  • 继续循环迭代是不必要的或不可能的。 这可能是由错误值或终止请求引起的。

  • 在 ...CatchTry捕获了异常...Finally.可以在块末尾Finally使用Exit For

  • 有一个无限循环,这是一个循环,可以运行大量甚至无限次数的循环。 如果检测到此类条件,可以使用 Exit For 来跳出循环。 有关详细信息,请参阅 Do...Loop 语句

迭代器

使用 迭代器 对集合执行自定义迭代。 迭代器可以是函数或 Get 访问器。 它使用语句 Yield 一次返回集合的每个元素。

使用 For Each...Next 语句调用迭代器。 循环的每个 For Each 迭代都会调用迭代器。 Yield在迭代器中到达语句时,将返回语句中的Yield表达式,并保留代码中的当前位置。 下次调用迭代器时,将从该位置重启执行。

以下示例使用迭代器函数。 迭代器函数有一个Yield位于 For... 中的语句下一个循环。 在 ListEvenNumbers 方法中,语句正文的每个迭代 For Each 都会创建对迭代器函数的调用,该函数将转到下一个 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

有关详细信息,请参阅 迭代器Yield 语句迭代器

技术实现

当 ...For Each Next 语句运行,Visual Basic 仅在循环启动前一次计算集合。 如果语句阻止更改 elementgroup这些更改不会影响循环的迭代。

将集合中的所有元素相继赋给 element时, For Each 循环将停止和控制传递给语句后面的 Next 语句。

如果 Option Infer 处于打开状态(其默认设置),则 Visual Basic 编译器可以推断其 element数据类型。 如果它已关闭且 element 尚未在循环外部声明,则必须在 For Each 语句中声明它。 若要显式声明数据类型 element ,请使用子 As 句。 除非元素的数据类型在 ...Next 构造之外For Each定义,否则其范围是循环的主体。 请注意,不能在循环内外声明 element

可以选择在Next语句中指定element。 这可以提高程序的可读性,尤其是在你拥有嵌套 For Each 循环时。 必须指定与相应 For Each 语句中显示的变量相同的变量。

你可能希望避免更改循环内的值 element 。 这样做会使读取和调试代码更加困难。 更改值 group 不会影响集合或其元素,这些元素是在首次输入循环时确定的。

嵌套循环时,如果在 Next 内部级别之前 Next 遇到外部嵌套级别的语句,编译器会发出错误信号。 只有当在每个element语句中指定Next时,编译器才能检测到此重叠错误。

如果代码依赖于按特定顺序遍历集合, For Each则 ...Next 循环不是最佳选择,除非你知道集合公开的枚举器对象的特征。 遍历顺序不是由 Visual Basic 确定的,而是由 MoveNext 枚举器对象的方法决定的。 因此,可能无法预测集合中第一个返回 element的元素,或者指定元素之后要返回的下一个元素。 可以使用其他循环结构(如 For...NextDo...Loop)实现更可靠的结果。

运行时必须能够将元素groupelement转换为 。 [Option Strict] 语句控制是允许加宽和缩小转换(Option Strict 关闭,其默认值),还是只允许扩大转换(Option Strict 打开)。 有关详细信息,请参阅 缩小转换范围

group数据类型必须是引用集合或可枚举的数组的引用类型。 通常, group 这意味着引用实现 IEnumerable 命名空间的接口 System.CollectionsIEnumerable<T> 命名空间接口的对象 System.Collections.GenericSystem.Collections.IEnumerable GetEnumerator定义方法,该方法返回集合的枚举器对象。 枚举器对象实现System.Collections.IEnumerator命名空间的System.Collections接口,并公开Current属性和ResetMoveNext方法和方法。 Visual Basic 使用这些函数遍历集合。

收缩转换

如果 Option Strict 设置为 On,缩小转换通常会导致编译器错误。 For Each但是,在语句中,从元素group转换到element的转换在运行时计算和执行,并禁止显示由缩小转换导致的编译器错误。

在以下示例中,作为初始值的赋值mn在打开时Option Strict不会编译,因为转换到 an Integer 是一个缩小的Long转换。 但是,在语句中For Each,不会报告编译器错误,即使要从LongInteger中转换到的赋值number也需要相同的转换。 在包含大量数字的语句中 For Each ,在应用于大数时 ToInteger 会发生运行时错误。

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

IEnumerator 调用

执行 For Each...Next 循环时,Visual Basic 会验证引用 group 有效的集合对象。 如果没有,它将引发异常。 否则,它将调用 MoveNext 枚举器对象的方法和 Current 属性以返回第一个元素。 如果 MoveNext 指示没有下一个元素,也就是说,如果集合为空,循环 For Each 将停止和控制传递给语句后面的 Next 语句。 否则,Visual Basic 将设置为 element 第一个元素并运行语句块。

每次 Visual Basic 遇到 Next 该语句时,它都会返回到 For Each 该语句。 再次调用 MoveNextCurrent 返回下一个元素,并再次运行块或停止循环,具体取决于结果。 此过程将继续执行,直到 MoveNext 指示没有下一个 Exit For 元素或遇到语句。

修改集合。 通常返回 GetEnumerator 的枚举器对象不允许通过添加、删除、替换或重新排序任何元素来更改集合。 如果在启动 For Each...Next 循环后更改集合,则枚举器对象将变为无效,下次尝试访问元素会导致异常 InvalidOperationException

但是,这种修改阻止不是由 Visual Basic 决定的,而是由接口的实现决定。IEnumerable 可以采用允许在迭代期间进行修改的方式实现 IEnumerable 。 如果考虑进行此类动态修改,请确保了解所使用的集合上实现的特征 IEnumerable

修改集合元素。 Current枚举器对象的属性为 ReadOnly,并返回每个集合元素的本地副本。 这意味着不能在 ...Next 循环中For Each修改元素本身。 所做的任何修改仅影响本地副本, Current 不会反映在基础集合中。 但是,如果元素是引用类型,则可以修改它指向的实例的成员。 以下示例修改 BackColor 每个 thisControl 元素的成员。 但是,不能修改 thisControl 自身。

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

前面的示例可以修改 BackColor 每个 thisControl 元素的成员,尽管它不能修改 thisControl 自身。

遍历数组。 Array由于该类实现IEnumerable接口,因此所有数组都会公开该方法GetEnumerator。 这意味着可以使用 ...Next循环循环访问数组For Each。 但是,只能读取数组元素。 不能更改它们。

示例 1

以下示例使用 DirectoryInfo 类列出 C:\ 目录中的所有文件夹。

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

示例 2

下面的示例演示了对集合进行排序的过程。 该示例对存储在 .. 中的List<T>类的Car实例进行排序。 该 Car 类实现 IComparable<T> 接口,这要求 CompareTo 实现该方法。

每次调用 CompareTo 该方法都会进行一次用于排序的比较。 方法中的 CompareTo 用户编写代码返回一个值,用于将当前对象与另一个对象进行比较。 如果当前对象小于另一个对象,则返回的值小于零,如果当前对象大于其他对象,则返回的值小于零;如果它们相等,则大于零。 这样,便可以在代码中定义大于、小于和等于的条件。

在方法中 ListCarscars.Sort() 语句对列表进行排序。 对 SortList<T> 方法的此调用将导致为 CompareTo 中的 Car 对象自动调用 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

另请参阅