For Each...Next 语句 (Visual Basic)

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

语法

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

组成部分

术语 定义
element 在 语句 For Each 中是必需的。 在 语句中 Next 为可选。 变量。 用于浏览集合的元素。
datatype 如果 为 on, (默认) 或已声明;如果 已关闭且尚未声明, Option Infer element Option Infer element 则是必需的。 element 的数据类型。
group 必需。 类型为集合类型或 Object 的变量。 引用要重复 statements 的集合。
statements 可选。 和 之间对 中每个 For Each Next 项运行的一个或多个 语句 group
Continue For 可选。 将控制转移到循环 For Each 的开始。
Exit For 可选。 将控制转移到循环 For Each 外。
Next 必需。 终止循环 For Each 的定义。

简单示例

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

提示

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

在下面的示例中, 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

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

Nested Loops

可以通过将 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 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 通常在计算某些条件后使用 If Then ,例如......Else 结构。 可能需要将 用于 Exit For 以下条件:

  • 继续进行重复访问是不必要或不可能的。 这可能是由错误值或终止请求引起的。

  • 在 中捕获异常 Try ... Catch...Finally.您可以在 Exit For 块的末尾 Finally 使用 。

  • 无限循环是一个循环,可以运行大量甚至无限次。 如果检测到此类情况,可以使用 Exit For 对循环进行转义。 有关详细信息,请参阅 执行...循环语句

迭代器

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

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

以下示例使用 iterator 函数。 iterator 函数有 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

有关详细信息,请参阅Iterators、Yield语句Iterator

技术实现

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

当集合中所有元素都连续分配给 时,循环将停止,并且控制将传递给 语句 element For EachNext 语句。

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

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

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

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

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

运行时必须能够将 中的元素转换为 group element 。 [ ] 语句控制是否允许扩大转换和收缩转换 (关闭、其默认值) ,或者是否仅允许扩大转换 (启用 Option Strict Option Strict Option Strict) 。 有关详细信息,请参阅 收缩转换

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

收缩转换

Option Strict 设置为 On 时,收缩转换通常会导致编译器错误。 但是,在语句中,将 For Eachgroup 运行时计算和执行从中的元素到的转换 element ,并取消收缩转换导致的编译器错误。

在下面的示例中,当为 m on 时,的赋值 n 不会进行编译, Option Strict 因为从到的转换 Long Integer 是收缩转换。 但是,在语句中, For Each 不会报告编译器错误,即使赋值 number 需要从到的相同转换 Long Integer 。 在 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枚举器对象的属性是只读的,它返回每个集合元素的本地副本。 这意味着不能在 For Each ... 循环中修改元素本身。 Next 您所做的任何修改只会影响的本地副本 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 方法。 这意味着可以使用 For Each ... 循环来循环访问数组。 Next 但是,只能读取数组元素。 不能更改它们。

示例 1

下面的示例列出了 C:\ 中的所有文件夹目录 DirectoryInfo

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

示例 2

以下示例阐释了对集合排序的过程。 该示例对中存储的类的实例进行排序 Car List<T>Car 类实现 IComparable<T> 接口,此操作需要实现 CompareTo 方法。

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

ListCars 方法中,cars.Sort() 语句对列表进行排序。 对 List<T>Sort 方法的此调用将导致为 List 中的 Car 对象自动调用 CompareTo 方法。

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

另请参阅