Quick Reference
반복문의 선택은 syntax 길이가 아니라 무엇이 다음 반복을 결정하는지로 합니다. index와 갱신이 중심이면 for, sequence를 순회하면 foreach, 종료 조건이 중심이면 while, 본문을 적어도 한 번 실행해야 하면 do를 씁니다.
for (int index = 0; index < items.Count; index++) { }
foreach (string name in names) { }
while (queue.TryDequeue(out var item)) { Process(item); }
do { input = Read(); } while (string.IsNullOrWhiteSpace(input));for의 initializer, condition, iterator는 각각 생략할 수 있습니다.for (;;)는 명시적인 무한 반복입니다.foreach는IEnumerable뿐 아니라GetEnumerator/MoveNext/Currentpattern을 만족하는 type에도 적용됩니다.break는 가장 안쪽 loop/switch를 끝내고,continue는 다음 iteration으로 갑니다.- 열거 중 수정 가능 여부는 collection의 enumerator contract를 확인합니다.
List<T>의 구조 변경을 일반foreach중에 하면 실패합니다.
네 반복문의 실행 시점
for (int index = 0; index < 3; index++)
{
Console.Write(index); // 012
}
int before = 0;
while (before < 0)
{
before++;
} // 0회
int after = 0;
do
{
after++;
} while (after < 0); // 1회for의 iterator section은 본문이 정상 종료하거나 continue를 만난 뒤 실행됩니다. while/do에서 continue는 조건 검사로 바로 이동합니다. 종료 조건을 바꾸는 statement가 실제로 실행되는지 확인하지 않으면 무한 loop가 됩니다.
for (int index = 0; index < 10; index++)
{
if (index % 2 == 0) continue; // iterator의 index++ 뒤 다음 조건 검사
if (index == 7) break; // 이 for만 종료
}중첩 loop에서 break는 가장 안쪽 loop만 끝냅니다. 바깥 loop 또는 method까지 빠져나가야 하면 flag, return, 별도 method, 제한적으로 goto label 중 흐름을 가장 분명히 보여 주는 방식을 고릅니다.
foreach와 collection 수정
foreach는 IEnumerable<T>만 요구하지 않습니다. public GetEnumerator(), MoveNext(), Current pattern이 있는 type도 순회할 수 있고, ref-return Current를 제공하면 foreach (ref ...) 또는 foreach (ref readonly ...)도 가능합니다.
List<T>는 enumerating 중 추가·삭제 같은 구조 변경을 감지하면 일반적으로 InvalidOperationException을 던집니다. 이는 모든 collection 또는 모든 mutation의 보편 규칙이 아닙니다. concurrent collection, custom enumerator, element object의 field 변경은 각 API의 계약을 따릅니다.
var scores = new List<int> { 10, -1, 20, -1 };
scores.RemoveAll(score => score < 0); // List<T>의 predicate 삭제 API
for (int index = scores.Count - 1; index >= 0; index--)
{
if (scores[index] < 0) scores.RemoveAt(index); // index 삭제는 역순
}
foreach (int score in scores.ToList())
{
// snapshot을 순회. allocation과 stale snapshot 비용을 감수
}filter·projection이 목적이면 Where/Select를, in-place deletion이면 collection이 제공하는 API 또는 index strategy를 사용합니다. foreach를 억지로 index loop로 바꾸기보다 mutation ownership과 collection 종류를 먼저 봅니다.
비동기와 선택 기준
await foreach는 IAsyncEnumerable<T> 또는 async enumeration pattern에서 다음 요소를 비동기로 받을 때 씁니다. 일반 foreach에 await를 넣는 것과 data source의 async enumeration은 다른 계약입니다.
| 상황 | 선택 | 확인할 것 |
|---|---|---|
| index·범위·역순 삭제 | for | index 갱신과 bounds |
| 읽기 중심 sequence 순회 | foreach | null source와 enumerator contract |
| queue·stream·상태 조건 | while | exit condition과 cancellation |
| prompt/retry처럼 최소 1회 | do | body가 실제로 먼저 실행됨 |
| async stream | await foreach | cancellation·disposal·context |
자주 틀리는 부분
foreach iteration variable에 새 값을 대입할 수 없고, foreach 자체가 모든 object mutation을 막지도 않습니다. 구조 변경과 element state 변경을 구분해 collection 문서를 확인하세요.
loop condition에 side effect를 숨기거나 continue 위에 increment를 두면 control flow가 쉽게 깨집니다. 갱신이 반복 형식의 핵심이면 for iterator section 또는 명시적인 loop tail에 둡니다.
참고 링크
2 sources