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 선택 사항. group의 각 항목에서 실행되는 For EachNext 사이의 하나 이상의 문입니다.
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 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 변수가 있어야 합니다.

또한 서로 다른 종류의 컨트롤 구조를 중첩할 수도 있습니다. 자세한 내용은 중첩된 컨트롤 구조를 참조하세요.

For 종료 및 For 계속

Exit For 문은 실행이 For를 종료하도록 합니다…Next는 루프를 반복하고 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 문의 식이 반환되고 코드의 현재 위치가 보존됩니다. 다음에 반복기가 호출되면 해당 위치에서 실행이 다시 시작됩니다.

다음 예에서는 반복기 함수를 사용합니다. 반복기 함수에는 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은 루프가 시작되기 전에 한 번만 컬렉션을 평가합니다. 문 블록이 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...Next 또는 Do...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 속성과 ResetMoveNext 메서드를 노출합니다. Visual Basic에서는 이를 사용하여 컬렉션을 탐색합니다.

축소 변환

Option StrictOn으로 설정된 경우 축소 변환은 일반적으로 컴파일러 오류를 발생시킵니다. 그러나 For Each 문에서는 group의 요소에서 element로의 변환이 런타임에 평가 및 수행되며 변환 축소로 인해 발생하는 컴파일러 오류가 억제됩니다.

다음 예에서 Long에서 Integer로의 변환은 축소 변환이므로 n의 초기 값으로 m을 할당하면 Option Strict가 켜져 있을 때 컴파일되지 않습니다. 그러나 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이 유효한 컬렉션 개체를 참조하는지 확인합니다. 그렇지 않으면 예외가 throw됩니다. 그렇지 않으면 열거자 개체의 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 자체를 수정할 수는 없지만 각 thisControl 요소의 BackColor 멤버를 수정할 수 있습니다.

배열 트래버스. 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 클래스는 CompareTo 메서드가 구현되어야 하는 IComparable<T> 인터페이스를 구현합니다.

CompareTo 메서드를 호출할 때마다 정렬에 사용되는 단일 비교가 수행됩니다. CompareTo 메서드의 사용자 작성 코드는 다른 개체와 현재 개체의 각 비교에 대한 값을 반환합니다. 현재 개체가 다른 개체보다 작으면 반환되는 값이 0보다 작고, 현재 개체가 다른 개체보다 크면 0보다 크고, 같으면 0입니다. 이렇게 하면 보다 큼, 보다 작음 및 같음에 대한 조건을 코드에서 정의할 수 있습니다.

ListCars 메서드에서 cars.Sort() 문은 목록을 정렬합니다. List<T>Sort 메서드를 호출하면 ListCar 개체에 대해 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

참고 항목