반복문 `for`, `foreach`, `do`, `while`

반복문 for, foreach, do, while

여러 번 실행해야 하는 코드가 있을 때, 반복문(iteration statement)을 쓰면 같은 문장이나 문장 블록을 조건이 맞는 동안 계속 실행할 수 있어요. C#의 반복문은 for, foreach, do, while 네 가지로 나뉘는데, 각각 특정 Boolean 식의 값에 따라 본문(body)을 반복해요.

출처: Microsoft Learn

본문

반복문은 문장 하나 또는 문장 블록을 반복 실행하는 데 쓰여요. for은 지정한 Boolean 식이 true로 평가되는 동안 본문을 실행하고, foreach은 컬렉션의 요소를 하나씩 꺼내 각 요소마다 본문을 실행해요. do은 조건에 따라 본문을 한 번 이상 실행하고, while은 조건에 따라 본문을 아예 안 실행할 수도 있어요.

반복문의 본문 어디에서든 break을 쓰면 반복을 중간에 빠져나올 수 있어요. continue을 쓰면 현재 반복을 건너뛰고 다음 반복으로 넘어가죠.

[!INCLUDEcsharp-version-note]

for

for 문은 지정한 Boolean 식이 true로 평가되는 동안 문장이나 문장 블록을 실행해요. 아래 예시는 정수 카운터가 3보다 작은 동안 본문을 실행하는 for 문을 보여줘요.

:::code language="csharp" source="snippets/iteration-statements/ForStatement.cs" id="TypicalExample":::

위 예시에서 for 문을 이루는 요소들을 살펴볼게요.

  • 초기화(initializer) 섹션 — 반복문에 진입하기 전에 딱 한 번만 실행돼요. 보통 여기서 지역 루프 변수를 선언하고 초기화해요. 이 변수는 for 문 바깥에서는 접근할 수 없어요.

    위 예시의 초기화 섹션은 정수 카운터 변수를 선언하고 초기화해요.

    int i = 0
    
  • 조건(condition) 섹션 — 루프의 다음 반복을 실행할지 결정해요. true로 평가되거나 아예 없으면 다음 반복이 실행되고, 그렇지 않으면 반복문이 끝나요. 이 조건 섹션은 반드시 Boolean 식이어야 해요.

    위 예시의 조건 섹션은 카운터 값이 3보다 작은지 확인해요.

    i < 3
    
  • 반복자(iterator) 섹션 — 루프 본문이 한 번 실행될 때마다 그 뒤에 어떤 일이 일어날지를 정의해요.

    위 예시의 반복자 섹션은 카운터를 1씩 증가시켜요.

    i++
    
  • 본문 — 문장 하나 또는 문장 블록이면 돼요.

반복자 섹션에는 아래에 해당하는 문장 식을 콤마로 구분해서 원하는 만큼 넣을 수 있어요.

초기화 섹션에서 루프 변수를 선언하지 않는다면, 초기화 섹션에서도 위 목록의 식을 필요한 만큼 쓸 수 있어요. 아래 예시는 좀 덜 흔한 초기화·반복자 섹션 용법을 몇 가지 보여줘요 — 초기화 섹션에서 외부 변수에 값을 대입하기, 초기화 섹션과 반복자 섹션 양쪽에서 메서드 호출하기, 반복자 섹션에서 두 변수의 값을 바꾸기까리요.

:::code language="csharp" source="snippets/iteration-statements/ForStatement.cs" id="MultipleExpressions":::

for 문의 모든 섹션은 생략할 수 있어요. 예를 들어 아래 코드는 무한 for 루프를 정의해요.

:::code language="csharp" source="snippets/iteration-statements/ForStatement.cs" id="InfiniteLoop":::

foreach

foreach 문은 xref:System.Collections.IEnumerable?displayProperty=nameWithType 또는 xref:System.Collections.Generic.IEnumerable`1?displayProperty=nameWithType 인터페이스를 구현하는 타입의 인스턴스에 들어 있는 각 요소마다 문장이나 문장 블록을 실행해요. 다음 예시를 볼게요.

:::code language="csharp" source="snippets/iteration-statements/ForeachStatement.cs" id="WithIEnumerable":::

하지만 foreach 문은 이런 타입에만 쓸 수 있는 건 아니에요. 아래 조건을 모두 만족하는 타입의 인스턴스라면 어떤 타입이든 쓸 수 있어요.

  • 타입에 public이고 매개변수가 없는 GetEnumerator 메서드가 있다. GetEnumerator 메서드는 확장 메서드일 수도 있어요.
  • GetEnumerator 메서드의 반환 타입에 public Current 속성과, 반환 타입이 bool인 public GetEnumerator 없는 MoveNext 메서드가 있다.

아래 예시는 어떤 인터페이스도 구현하지 않는 xref:System.Span`1?displayProperty=nameWithType 타입의 인스턴스에 foreach 문을 쓰는 모습이에요.

:::code language="csharp" source="snippets/iteration-statements/ForeachStatement.cs" id="WithSpan" :::

열거자(enumerator)의 Current 속성이 참조 반환값(reference return value) — 다시 말해 컬렉션 요소 타입을 T라 했을 때 ref T — 을 반환한다면, 반복 변수를 refref readonly 한정자로 선언할 수 있어요. 다음 예시를 확인해 보세요.

:::code language="csharp" source="snippets/iteration-statements/ForeachStatement.cs" id="RefIterationVariable" :::

foreach 문의 소스 컬렉션이 비어 있으면 본문은 실행되지 않고 그냥 건너뛰어져요. 반대로 foreach 문을 null에 적용하면 xref:System.NullReferenceException이 던져져요.

await foreach

await foreach 문은 비동기 스트림 — 즉 xref:System.Collections.Generic.IAsyncEnumerable`1 인터페이스를 구현하는 컬렉션 타입 — 의 데이터를 소비할 때 써요. 루프의 각 반복은 다음 요소를 비동기적으로 가져오는 동안 잠시 중단될 수 있어요. await foreach 문 사용법을 아래 예시에서 보여드릴게요.

:::code language="csharp" source="snippets/iteration-statements/ForeachStatement.cs" id="AwaitForeach" :::

await foreach 문도 아래 조건을 충족하는 타입의 인스턴스라면 어떤 타입이든 함께 쓸 수 있어요.

  • 타입에 public이고 매개변수가 없는 GetAsyncEnumerator 메서드가 있다. 이 메서드는 확장 멤버일 수 있어요.
  • GetAsyncEnumerator 메서드의 반환 타입에 public Current 속성과, 반환 타입이 Task<bool>, ValueTask<bool> 또는 awaiter의 GetResult 메서드가 bool 값을 반환하는 다른 awaitable 타입인 public MoveNextAsync 메서드가 있다.

기본적으로 await foreach 문은 포착된 컨텍스트(captured context)에서 스트림 요소를 처리해요. 컨텍스트 포착을 끄고 싶다면 xref:System.Threading.Tasks.TaskAsyncEnumerableExtensions.ConfigureAwait*?displayProperty=nameWithType 확장 메서드를 사용하면 돼요. 동기화 컨텍스트와 현재 컨텍스트 포착에 대한 자세한 내용은 Task 기반 비동기 패턴 사용하기를, 비동기 스트림에 대한 자세한 내용은 비동기 스트림 자습서를 참고하세요.

반복 변수의 타입

foreach 문에서는 var 키워드를 써서 반복 변수의 타입을 컴파일러가 추론하도록 할 수 있어요. 아래 코드처럼요.

foreach (var item in collection) { }

[!NOTE] 컴파일러는 nullable 인식 컨텍스트가 켜져 있는지, 초기화 식의 타입이 참조 타입인지에 따라 var 선언의 타입을 nullable 참조 타입으로 추론할 수 있어요. 자세한 내용은 암시적으로 형식화된 지역 변수를 참고하세요.

반복 변수의 타입을 명시적으로 지정할 수도 있어요.

IEnumerable<T> collection = new T[5];
foreach (V item in collection) { }

위 형태에서 컬렉션 요소의 타입 T는 반복 변수의 타입 V암시적이든 명시적이든 변환될 수 있어야 해요. 실행 시점에 T에서 V로의 명시적 변환이 실패하면 foreach 문은 xref:System.InvalidCastException을 던져요. 예를 들어 T가 sealed(봉인)되지 않은 클래스 타입이라면, VT가 구현하지 않는 인터페이스 타입도 될 수 있어요. 실행 시점의 컬렉션 요소 타입이 T에서 파생된 타입이면서 실제로 V를 구현할 수도 있으니까요. 그렇지 않으면 xref:System.InvalidCastException이 던져져요.

do

do 문은 지정한 Boolean 식이 true로 평가되는 동안 문장이나 문장 블록을 실행해요. 이 루프는 실행할 때마다 마지막에 그 식을 평가하기 때문에, do 루프는 한 번 이상 실행돼요. do 루프는 아예 안 실행될 수도 있는 while 루프와 여기서 차이가 나요.

do 문 사용 예시를 볼게요.

:::code language="csharp" source="snippets/iteration-statements/DoStatement.cs" id="Example":::

while

while 문은 지정한 Boolean 식이 true로 평가되는 동안 문장이나 문장 블록을 실행해요. 이 루프는 실행하기 전에 먼저 그 식을 평가하기 때문에, while 루프는 아예 실행되지 않을 수도 있어요. while 루프는 한 번 이상 실행되는 do 루프와 여기서 차이가 나요.

while 문 사용 예시를 볼게요.

:::code language="csharp" source="snippets/iteration-statements/WhileStatement.cs" id="Example":::

C# 언어 사양

더 자세한 내용은 C# 언어 사양의 다음 섹션을 참고하세요.

더 알아보기