TIL_2025_03_31 foreach 삭제 오류

김효중·2025년 3월 31일

문제 상황

public class DitonaryTest : MonoBehaviour
{
    public Dictionary<string, bool> checkList;
    void Start()
    {
        checkList = new();
        checkList["aaa"] = true;
        checkList["bbb"] = false;
        checkList["ccc"] = true;
        checkList["ddd"] = false;

        // 요소 삭제
        foreach(var work in checkList)
        {
            if(work.Key == "ccc")
            {
                checkList.Remove("ccc");
            }
        }

        // 요소 출력
        foreach(var work in checkList)
        {
            Debug.Log(work.Key + " : " + work.Value);
        }
    }
}

데이터를 이름를 키로, bool값을 값으로 가지는 딕셔너리에서 딕셔너리의 특정 키값을 가지는 요소를 제거할려는 상황이였다.

InvalidOperationException: Collection was modified; enumeration operation may not execute.
System.Collections.Generic.Dictionary`2+Enumerator[TKey,TValue].MoveNext () (at <17d9ce77f27a4bd2afb5ba32c9bea976>:0)

문제의 원인

이 에러는 foreach안에서 해당 요소를 삭제하였을 때 생기는 문제이다.
foreach는 내부적으로 이터레이터(Enumerator)를 사용하여 컬렉션을 순회하는데
순회하는 동안 항목을 삭제하면 이터레이터가 예상치 못한 변경을 감지하고 예외를 발생시킨다.
그렇기에 foreach내에서 요소를 삭제한다면 딕셔너리, 리스트등을 가지리 않고 발생한다.

List<int> numbers = new List<int>{1, 2, 3, 4};
foreach(var num in numbers)
{
    if(num == 3)
    {
        numbers.Remove(3);
    }
}

예를 들어 위와 같이 리스트의 요소를 foreach내부에서 요소를 삭제하면 다음과 같은 오류가 발생한다.

InvalidOperationException: Collection was modified; enumeration operation may not execute.
System.Collections.Generic.List`1+Enumerator[T].MoveNextRare () (at <17d9ce77f27a4bd2afb5ba32c9bea976>:0)
System.Collections.Generic.List`1+Enumerator[T].MoveNext () (at <17d9ce77f27a4bd2afb5ba32c9bea976>:0)

위 에러도 역시 이터레이터가 변경을 감지 하였기에 예외를 발생시킨다.

문제의 해결

복사본으로 검사하고 원본을 제거 - ToList()

		foreach(var work in checkList.ToList())
        {
            if(work.Key == "ccc")
            {
                checkList.Remove("ccc");
            }
        }

위와 같이 TiList()를 통해 복사본을 만들고 원본 리스트만을 변경한다.
그렇기에 삭제하는 컬렉션과 탐색하는 컬렉션이 다르기 때문에 이터레이터의 영향을 받지 않는다.

이터레이터를 사용하지 않는 방법으로 변경 - RemoveAll()

	List<int> numbers = new List<int>{1, 2, 3, 4};
    numbers.RemoveAll(num => num == 3);

numbers와 같은 리스트의 같은 경우 foreach 이외의 방법을 사용하면 예외 사항이 발생하지 않는다.
RemoveAll는 내부적으로 for문을 이용한 탐색과 수정은 예외 사항이 발행하지 않는다.

결과 및 결론


위와 같이 정상적으로 요소가 삭제 되고 딕셔너리의 요소가 출력된다.
컬렉션을 순회하면서 수정을 할 경우에는 탐색하는 요소를 직접 수정/삭제하지 않고 ToList()를 사용하여 복사본을 만들어 원본의 탐색과 수정을 동시에 하지 않는 방법을 사용하도록 한다.

profile
도전하는 개발자

0개의 댓글