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 必須です。 コレクション型または Object である型を持つ変数。 statements が繰り返されるコレクションを参照します。
statements 任意。 group の各項目に対して実行される、For EachNext の間の 1 つ以上のステートメント。
Continue For 任意。 For Each ループの先頭に制御を移します。
Exit For 任意。 For Each ループから制御を移します。
Next 必須です。 For Each ループの定義を終了します。

簡単な例

コレクションまたは配列の要素ごとに一連のステートメントを繰り返す場合は、For Each...Next ループを使用します。

ヒント

For...Next ステートメント は、ループの各反復を制御変数に関連付けて、その変数の初期値と最終値を決定できる場合に適しています。 ただし、コレクションを処理する場合、初期値と最終値の概念は意味がなく、コレクションに含まれる要素の数がわかるとは限りません。 このような場合は、For Each...Next ループの方が適していることがよくあります。

次の例では、For Each...Next ステートメントは、リスト コレクションのすべての要素を反復処理します。

' 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 と Continue For

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

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 ステートメント」を参照してください。

Iterators

反復子 は、コレクションに対するカスタムの反復処理を実行するために使用します。 反復子は、関数または Get アクセサーのいずれかです。 これは、Yield ステートメントを使用して、コレクションの各要素を 1 回に 1 つ返します。

For Each...Next ステートメントを使用して、反復子を呼び出します。 For Each ループの各イテレーションは、反復子を呼び出します。 Yield ステートメントが反復子に到達すると、Yield ステートメント内の式が返され、コードの現在の位置が保持されます。 次回、反復子が呼び出されると、この位置から実行が再開されます。

次の例は、iterator 関数を使用します。 iterator 関数には、For…Next ループ内に Yield ステートメントがあります。 ListEvenNumbers メソッドでは、For Each ステートメント本文の各反復で iterator 関数が呼び出され、これが次の 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 ステートメント」、および「Iterator」を参照してください。

技術的な実装

For EachNext ステートメントを実行すると、ループが開始される前に、Visual Basic によってコレクションが 1 回だけ評価されます。 ステートメント ブロックで element または group が変更された場合、これらの変更はループの繰り返しに影響しません。

コレクション内のすべての要素が連続して 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 は、列挙可能なコレクションまたは配列を参照する参照型である必要があります。 ほとんどの場合、これは、groupSystem.Collections 名前空間の IEnumerable インターフェイスまたは System.Collections.Generic 名前空間の IEnumerable<T> インターフェイスを実装するオブジェクトを参照することを意味します。 System.Collections.IEnumerable は、コレクションの列挙子オブジェクトを返す GetEnumerator メソッドを定義します。 列挙子オブジェクトは、System.Collections 名前空間の System.Collections.IEnumerator インターフェイスを実装し、Current プロパティと Reset および MoveNext メソッドを公開します。 Visual Basic では、これらを使用してコレクションを走査します。

縮小変換

Option StrictOn に設定されている場合、縮小変換では、通常はコンパイラ エラーが発生します。 ただし、For Each ステートメントでは、group 内の要素から element への変換は実行時に評価されて実行され、縮小変換によって発生するコンパイラ エラーは抑制されます。

次の例では、n の初期値としての m の割り当ては、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 プロパティは 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() ステートメントがリストを並べ替えます。 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

関連項目