-
C# foreach 돌리다 터지는 '열거 작업 수정' 에러, 해결 가이드Enumerable was modified개발/C# 2026. 1. 19. 12:24반응형
안녕하세요!
C#으로 개발하다 보면 List나 Dictionary 같은 컬렉션을 정말 많이 쓰죠. 특히 foreach문은 가독성이 좋아서 우리가 애용하는 도구입니다. 그런데 가끔 기분 좋게 로직을 짜고 돌렸는데, 갑자기 이런 빨간 줄이 뜨면서 프로그램이 죽어버릴 때가 있습니다.
에러 메시지: System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' (컬렉션이 수정되었습니다. 열거 작업이 실행되지 않을 수 있습니다.)
도대체 내가 뭘 잘못했다고 이러는 걸까요? 오늘은 이 에러가 왜 발생하는지, 그리고 제가 실무에서 쓰는 '절대 안 터지는' 3가지 해결법을 공유해 드릴게요.
1. 왜 이런 에러가 날까요? (범인은 foreach)
이유는 아주 단순하면서도 엄격합니다. C#의 foreach는 **"내가 지금 이 목록을 읽고 있는 동안에는, 그 누구도 이 목록을 건드리지 마!"**라고 약속하고 돌아가기 때문입니다.
우리가 foreach로 리스트를 한 칸씩 읽고 있는데, 그 안에서 Remove()로 항목을 지우거나 Add()로 항목을 추가해버리면 어떻게 될까요? 컴퓨터 입장에서는 "어? 내가 방금 3번 읽고 있었는데, 하나가 빠져서 순서가 엉켰잖아! 나 이제 어디 읽어야 해?"라며 패닉에 빠지는 거죠.
이건 마치 달리고 있는 기차의 선로를 기차 안에서 고치는 것과 같습니다. 당연히 탈선(에러)이 일어날 수밖에 없죠.
2. 에러가 발생하는 흔한 상황 (Before)
보통 리스트에서 조건에 맞는 항목을 지울 때 이런 실수를 제일 많이 합니다.
List<string> items = new List<string> { "사과", "바나나", "포도", "딸기" }; foreach (var item in items) { if (item == "바나나") { // 여기서 에러가 터집니다. items.Remove(item); } }3. 해결 방법 3가지 (After)
그럼 어떻게 해야 할까요? 제가 상황별로 골라 쓰실 수 있게 3가지 방법을 가져왔습니다.
방법 1: 리스트를 뒤에서부터 훑기 (for 역순 순회)
제가 가장 추천하는 고전적이면서도 확실한 방법입니다. 뒤에서부터 지우면, 인덱스가 당겨져도 우리가 다음에 읽을 앞부분의 번호에는 영향이 없거든요!
for (int i = items.Count - 1; i >= 0; i--) { if (items[i] == "바나나") { items.RemoveAt(i); // 안전하게 삭제 완료! } }방법 2: 복사본으로 읽기 (.ToList())
"나는 죽어도 foreach를 써야겠다!" 하시는 분들은 원본이 아니라 복사본을 읽으면 됩니다. 복사본을 읽는 동안 원본을 수정하는 건 상관없으니까요.
// .ToList()로 복사본을 만들어 그걸 기준으로 읽습니다. foreach (var item in items.ToList()) { if (item == "바나나") { items.Remove(item); // 원본을 수정해도 에러가 안 납니다. } }주의: 리스트가 너무 크면 복사하는 데 메모리가 드니까 적당한 크기일 때만 쓰세요!
방법 3: 한 방에 지우기 (RemoveAll) - 가장 세련된 방법
C# 개발자라면 가장 추천하고 싶은 방식입니다. List에서 제공하는 전용 메서드를 쓰면 루프를 돌릴 필요조차 없습니다.
// "바나나"인 녀석들은 싹 다 지워줘! items.RemoveAll(item => item == "바나나");코드가 한 줄로 끝나죠? 가독성도 좋고 속도도 빠릅니다.
무자의 꿀팁: 어떤 걸 골라 써야 하나요?
- 가장 편한 방법: 방법 3 (RemoveAll). 코드가 깔끔해서 실수할 일이 없습니다.
- 복잡한 로직이 필요할 때: 방법 1 (역순 for문). 지우는 것 외에 다른 계산도 복잡하게 해야 한다면 이게 제일 자유도가 높습니다.
- 멀티스레드 환경: 만약 여러 곳에서 동시에 리스트를 건드린다면, 아예 ConcurrentBag 같은 안전한 컬렉션을 쓰는 게 정답입니다. (이건 나중에 심화 편에서 다뤄볼게요!)
마무리하며
저도 처음 개발할 때는 이 에러를 마주하고 "아니, 내가 지우겠다는데 왜 안 된다는 거야!"라며 짜증 냈던 기억이 납니다. ㅎㅎ 하지만 이게 다 프로그램이 엉뚱한 데이터를 읽지 않도록 지켜주는 든든한 보호 장치라는 걸 이해하고 나니 오히려 고맙더라고요.
저도 처음엔 이거 몰라서 리스트를 새로 만들고 난리도 아니었네요
반응형'개발 > C#' 카테고리의 다른 글
[C#] "파일을 찾을 수 없습니다" – FileNotFoundException, 경로의 함정 (1) 2026.01.22 [C#] Struct vs Class, 성능 차이 진짜 날까? (언제 무엇을 쓸지 정해드립니다) (0) 2026.01.20 [C#] WinForms "크로스 스레드 작업이 잘못되었습니다" (0) 2026.01.15 C# 비동기 프로그래밍(async/await) (1) 2026.01.14 C# 문자열 합치기, 아직도 + 연산자만 쓰시나요? (String vs StringBuilder) (0) 2026.01.13