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 如果 Option Infer 為開啟 (預設) 或已宣告 element,則為選用;如果 Option Infer 為關閉,而且尚未宣告 element,則為必要。 element的資料型別。
group 必要。 具有集合型別或物件型別的變數。 參考將重複的 statements 集合。
statements 選擇性。 位於 For EachNext 之間而且在 group 中的每個項目上執行的一個或多個陳述式。
Continue For 選擇性。 將控制權傳輸至 For Each 迴圈的開頭。
Exit For 選擇性。 將控制權移出 For Each 迴圈。
Next 必要。 終止 For Each 迴圈的定義。

簡單範例

您想要針對集合或陣列的每個元素重複一組陳述式時,請使用 For Each...Next 迴圈。

提示

您可以將迴圈的每個反覆項目與控制項變數產生關聯,並判斷該變數的初始和最終值時,For...Next 陳述式可以正常運作。 不過,您處理集合時,初始和最終值的概念並沒有意義,而且不一定知道集合有多少元素。 在這種情況下,For Each...Next 迴圈通常是較佳的選擇。

在下列範例中,For EachNext 陳述式會逐一查看清單集合的所有元素。

' 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 EachNext 結構。

' 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 陳述式會導致執行結束 ForNext 迴圈,並將控制權轉移至 Next 陳述式之後的陳述式。

Continue For 陳述式會立即將控制權轉移至迴圈的下一次反覆運算。 如需詳細資訊,請參閱 Continue 陳述式

下列範例顯示如何使用 Continue ForExit 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

您可以在 For Each 迴圈中放入任意數目的 Exit For 陳述式。 在巢狀 For Each 迴圈內使用時,Exit For 會導致執行結束最內層的迴圈,並將控制權傳輸至下一個較高的巢狀層級。

Exit For 通常會在評估某些情況之後使用,例如,在 If...Then...Else 結構中。 在下列情況中,您可能會想使用 Exit For

  • 繼續逐一查看是不必要的或不可能的。 這可能是因為錯誤值或終止要求所造成。

  • Try...Catch...Finally 中攔截到例外狀況。您可以在 Finally 區塊結尾使用 Exit For

  • 有無限迴圈,可用於執行大量或甚至無限次數的迴圈。 如果您偵測到該情況,則可以使用 Exit For 來逸出迴圈。 如需詳細資訊,請參閱 Do...Loop 陳述式

迭代器

您可以使用「迭代器」在集合上執行自訂反覆項目。 迭代器可以是語言函式或 Get 存取子。 這會使用 Yield 陳述式,一次一個地傳回集合中的每個元素。

您會使用 For Each...Next 陳述式來呼叫迭代器。 For Each 迴圈的每個反覆項目都會呼叫迭代器。 在迭代器中到達 Yield 陳述式時,會傳回 Yield 陳述式中的運算式,並保留程式碼中的目前位置。 下一次呼叫迭代器時,便會從這個位置重新開始執行。

下列範例使用了 iterator 語言函式。 迭代器語言函式具有 For…Next 迴圈內的 Yield 陳述式。 在 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 子句。 除非元素的資料型別定義在 For Each ... Next 建構之外,否則其範圍是迴圈的主體。 請注意,您無法在迴圈外部和內部宣告 element

您可以選擇在 Next 陳述式中指定 element。 這可改善程式的可讀性,在您擁有巢狀 For Each 迴圈的情況下更是如此。 您必須指定與對應 For Each 陳述式中出現的變數相同的變數。

您可能想要避免在迴圈內變更 element 的值。 這麼做會讓您更難以閱讀和偵錯您的程式碼。 變更 group 的值不會影響第一次輸入迴圈時所決定的集合或其元素。

您使迴圈成為巢狀時,如果在內部層級的 Next 之前遇到外部巢狀層級的 Next 陳述式,編譯器就會發出錯誤訊號。 不過,只有在您在每個 Next 陳述式中指定 element 時,編譯器才能偵測到這個重迭的錯誤。

如果您的程式碼需要以特定順序周遊集合,除非您知道集合所公開的列舉值物件特性,否則 For Each...Next 迴圈不是最佳選擇。 周遊的順序不是由 Visual Basic 決定,而是由列舉值物件的 MoveNext 方法決定。 因此,您可能無法預測 element 會先傳回集合中的哪個元素,或將在指定的元素之後要接著傳回哪個元素。 您可以使用不同的迴圈結構來達到更可靠的結果,例如 For ... NextDo ... Loop

執行階段必須能夠將 group 中的元素轉換成 element。 [Option Strict] 陳述式可控制是否允許擴大和縮小轉換 (Option Strict 關閉,其預設值),或是否允許放大轉換 (Option Strict 開啟)。 如需詳細資訊,請參閱縮小轉換

group 的資料型別必須是參考集合的參考型別,或是可列舉的陣列。 最常見的情況是,這表示 group 參考實作 System.Collections 命名空間 IEnumerable 介面或 System.Collections.Generic 命名空間 IEnumerable<T> 介面的物件。 System.Collections.IEnumerable 定義 GetEnumerator 方法,這個方法會傳回集合的列舉值物件。 列舉值物件會實作 System.Collections 命名空間 System.Collections.IEnumerator 介面,並公開 Current 屬性以及 ResetMoveNext 方法。 Visual Basic 會使用這些來周遊集合。

縮小轉換

Option Strict 設定為 On 時,縮小轉換通常會造成編譯器錯誤。 不過,在 For Each 陳述式中,從 group 中的元素轉換為 element 的過程會在執行階段進行評估和執行,並隱藏縮小轉換所造成的編譯器錯誤。

在下列範例中,Option Strict 開啟時,做為 n 的初始值而指派 的 m 不會編譯,因為 Long 轉換成 Integer 的過程是縮小轉換。 不過,在 For Each 陳述式中,即使指派到 number 需要從 LongInteger 的相同轉換,也不會報告編譯器錯誤。 在包含大量資料的 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,它會傳回每個集合元素的本機複本。 這表示您無法在 For Each...Next 迴圈中修改元素本身。 您所做的任何修改只會影響來自 Current 的本機複本,而且不會反映回基礎集合中。 不過,如果元素是參考型別,您可以修改它所指向的執行個體成員。 下列範例會修改每個 thisControl 元素的 BackColor 成員。 不過,您無法修改 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

上一個範例可以修改每個 thisControl 元素的 BackColor 成員,但無法修改 thisControl 本身。

周遊陣列。 因為 Array 類別會實作 IEnumerable 介面,所以所有陣列都會公開 GetEnumerator 方法。 這表示您可以使用 For Each...Next 迴圈逐一查看陣列。 不過,您只能讀取陣列元素。 您無法變更這些元素。

範例 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 方法中的使用者撰寫程式碼會傳回值。 如果目前物件比另一個物件小則傳回的值小於零,如果目前物件比另一個物件大則傳回的值大於零,如果它們相等則傳回零。 這可讓您以程式碼定義大於、小於、等於的準則。

ListCars 方法中,cars.Sort() 陳述式會排序清單。 對 List<T>Sort 方法的這個呼叫,會導致 CompareTo 方法對 ListCar 物件自動呼叫。

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

另請參閱