Computer >> 컴퓨터 >  >> 프로그래밍 >> C#

C# "컬렉션이 수정되어 열거 작업이 실행되지 않을 수 있습니다" 오류의 원인과 해결 방법

C#에서 "Collection was modified; enumeration operation may not execute"(컬렉션이 수정되어 열거 작업이 실행되지 않을 수 있습니다) 오류는 List와 같은 컬렉션을 foreach 루프로 순회하는 도중에 해당 컬렉션에 요소를 추가하거나 삭제하는 등의 변경이 일어날 때 발생합니다.

오류가 발생하는 예제

using System;
using System.Collections.Generic;
namespace DemoApplication {
    public class Program {
        static void Main(string[] args) {
            try {
                var studentsList = new List<Student> {
                    new Student {
                        Id = 1,
                        Name = "John"
                    },
                    new Student {
                        Id = 0,
                        Name = "Jack"
                    },
                    new Student {
                        Id = 2,
                        Name = "Jack"
                    }
                };
                foreach (var student in studentsList) {
                    if (student.Id <= 0) {
                        studentsList.Remove(student);
                    }
                    else {
                        Console.WriteLine($"Id: {student.Id}, Name: {student.Name}");
                    }
                }
            }
            catch(Exception ex) {
                Console.WriteLine($"Exception: {ex.Message}");
                Console.ReadLine();
            }
        }
    }
    public class Student {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

실행 결과

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

Id: 1, Name: John
Exception: Collection was modified; enumeration operation may not execute.

오류가 발생하는 이유

위 예제에서는 foreach 루프를 사용해 studentsList를 순회하고 있습니다. 학생의 Id가 0이면 해당 항목을 studentsList에서 제거하는데, 바로 이 시점에 컬렉션의 크기가 변경(재조정)되면서 런타임 중에 예외가 발생합니다.

foreach 문은 내부적으로 열거자(Enumerator)를 사용해 컬렉션을 순회하는데, 이 열거자는 컬렉션의 버전 정보를 추적합니다. 순회 도중 컬렉션이 수정되면 버전이 달라져 열거자가 더 이상 유효하지 않다고 판단하고 예외를 던지는 것입니다.

해결 방법

이 문제를 해결하는 가장 간단한 방법은 반복을 시작하기 전에 ToList() 메서드를 호출하여 원본 컬렉션의 복사본을 만든 뒤, 그 복사본을 대상으로 순회하는 것입니다.

foreach (var student in studentsList.ToList())

ToList()는 LINQ 확장 메서드로, 원본 리스트와는 별개인 새로운 리스트를 반환합니다. 따라서 복사본을 순회하는 동안 원본 컬렉션에서 요소를 안전하게 제거할 수 있고, 열거자 충돌 없이 코드가 정상적으로 동작합니다.

수정된 전체 예제

using System;
using System.Collections.Generic;
using System.Linq;
namespace DemoApplication {
    public class Program {
        static void Main(string[] args) {
            var studentsList = new List<Student> {
                new Student {
                    Id = 1,
                    Name = "John"
                },
                new Student {
                    Id = 0,
                    Name = "Jack"
                },
                new Student {
                    Id = 2,
                    Name = "Jack"
                }
            };
            foreach (var student in studentsList.ToList()) {
                if (student.Id <= 0) {
                    studentsList.Remove(student);
                }
                else {
                    Console.WriteLine($"Id: {student.Id}, Name: {student.Name}");
                }
            }
            Console.ReadLine();
        }
    }
    public class Student {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

실행 결과

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

Id: 1, Name: John
Id: 2, Name: Jack

참고: 다른 해결 방법들

ToList() 외에도 상황에 따라 아래 방법들을 활용할 수 있습니다.

  • 역방향 for 루프 사용: 인덱스 기반 for 루프를 뒤에서부터 앞으로 순회하면 요소를 제거해도 인덱스가 밀리지 않아 안전합니다.
  • RemoveAll() 사용: 조건에 맞는 요소를 한 번에 제거하는 RemoveAll(student => student.Id <= 0)처럼 람다식을 활용하면 별도의 루프 없이 깔끔하게 처리할 수 있습니다.
  • 새 컬렉션 생성: Where() 같은 LINQ 메서드로 필터링된 새 리스트를 만들어 원본을 교체하는 방식도 좋은 선택입니다.