숏컷 코드
// 인덱스가 필요할 때
for (int i = 0; i < items.Count; i++)
{
Console.WriteLine($"[{i}] {items[i]}");
}
// 컬렉션을 읽기만 할 때
foreach (string name in names)
{
Console.WriteLine(name);
}
// 종료 시점이 조건 기반일 때
while (!queue.IsEmpty)
{
Process(queue.Dequeue());
}문법
네 가지 반복문은 문법이 다른 게 아니라, 어떤 반복 구조를 표현하려는가가 다릅니다.
어떤 반복문을 먼저 고르면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 인덱스가 필요함 | for |
| 읽기 전용 컬렉션 순회 | foreach |
| 종료 시점이 조건 기반 | while |
| 본문을 최소 한 번 실행 | do-while |
for — 횟수와 범위가 중요한 반복
카운터 변수, 종료 조건, 증감 식이 한 줄에 모여 있어서 "몇 번, 어디서 어디까지"가 한눈에 보입니다. 인덱스로 이전/다음 항목에 동시에 접근해야 하거나, 반복하면서 컬렉션을 직접 수정해야 할 때 유일하게 안전한 선택지입니다.
foreach — 컬렉션을 읽기 위한 반복
foreach는 내부적으로 IEnumerable<T>의 GetEnumerator()를 호출해서 열거자를 만들고, MoveNext()로 한 칸씩 전진하며 Current로 현재 값을 읽습니다. 컴파일러는 아래처럼 변환합니다:
// foreach (var item in items) { ... } 의 실제 동작
IEnumerator<Item> e = items.GetEnumerator();
try
{
while (e.MoveNext())
{
Item item = e.Current;
// 본문
}
}
finally
{
e.Dispose();
}이 구조 때문에 foreach 도중 컬렉션을 수정하면 InvalidOperationException이 발생합니다. 열거자는 컬렉션의 내부 버전 번호를 기억하는데, 항목이 추가·삭제되면 번호가 바뀌어 열거자가 즉시 이를 감지합니다.
while — 횟수를 모를 때의 반복
반복 횟수를 미리 알 수 없고, 조건이 충족될 때까지 계속 실행해야 할 때 씁니다. 네트워크 폴링, 큐 처리, 게임 루프처럼 "끝날 때까지"가 기준인 패턴이 여기 해당합니다.
// ❌ Count 기반인데 while로 쓰면 갱신 위치를 놓치기 쉽다
int i = 0;
while (i < items.Count)
{
Console.WriteLine(items[i]);
i++;
}
// ✅ 횟수와 범위가 명확하면 for가 더 직접적
for (int i = 0; i < items.Count; i++)
{
Console.WriteLine(items[i]);
}do-while — 최소 한 번은 실행해야 할 때
while과 거의 같지만 조건을 나중에 검사해서 본문이 반드시 한 번은 실행됩니다. 입력을 먼저 받고 유효성을 검사하는 패턴에 자연스럽습니다:
string input;
do
{
Console.Write("양수를 입력하세요: ");
input = Console.ReadLine() ?? "";
}
while (!int.TryParse(input, out int n) || n <= 0);반복 제어
break, continue, return은 반복 흐름을 다르게 끊습니다:
foreach (var order in orders)
{
if (order.IsCancelled) continue; // 이번 항목만 건너뜀, 반복 계속
if (order.IsBlocked) break; // 반복 전체 즉시 종료
if (!Validate(order)) return; // 메서드 자체를 종료
}break는 반복문만 끝내고 그 다음 코드를 이어 실행합니다. return은 반복문을 포함한 메서드 자체를 빠져나옵니다. 의도에 맞는 쪽을 명확히 써야 이후 코드가 예상대로 실행됩니다.
체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 인덱스 접근이 필요함 | for |
| 반복 중 컬렉션을 수정해야 함 | for (역방향 순회 고려) |
| 컬렉션을 읽기만 함 | foreach |
| 반복 횟수를 모름, 조건 기반 종료 | while |
| 최소 한 번은 반드시 실행해야 함 | do-while |
| 필터링·변환이 주목적 | LINQ (Where, Select) 고려 |
주의할 점
foreach 도중 컬렉션을 수정하면 예외가 발생합니다.
// ❌ InvalidOperationException
foreach (var item in list)
{
if (item.IsExpired)
list.Remove(item);
}
// ✅ 삭제 대상을 먼저 수집한 뒤 처리
var expired = list.Where(x => x.IsExpired).ToList();
foreach (var item in expired)
list.Remove(item);
// ✅ 또는 역방향 for (인덱스가 안정적)
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i].IsExpired)
list.RemoveAt(i);
}while은 종료 조건에 관여하는 값이 루프 안에서 실제로 바뀌고 있는지 반드시 확인해야 합니다. 조건이 항상 참이면 무한 루프가 되고, 프로세스가 멈추지 않습니다.
foreach는 읽기 용도에 가장 자연스럽지만, 현재 인덱스가 필요하거나 현재 항목을 제거해야 하는 순간에는 for가 더 맞습니다. 억지로 foreach 안에서 상태를 바꾸기 시작하면 예외나 가독성 저하가 곧바로 따라옵니다.
참고 링크
2 sources