본문 바로가기
내가겪은에러들

[유니티] C# Dictionary 순회 중 삭제 시 발생하는 오류 및 해결 방법.

by 정보모아모아 2016. 3. 14.

C# Dictionary 순회 중 삭제 시 발생하는 오류 및 해결 방법

C#에서 Dictionary와 같은 컬렉션을 사용할 때, 특정 조건에 따라 요소를 삭제해야 하는 경우가 발생합니다. 이때 흔히 저지르는 실수가 순회(iteration) 도중에 바로 삭제를 시도하는 것입니다. 이 글에서는 이러한 경우 발생하는 오류를 설명하고, 올바른 해결 방법을 제시합니다.

문제 상황

다음과 같은 코드를 생각해 보겠습니다. m_dicPathDictionary<int, Path> 타입의 딕셔너리이고, Path 객체의 PATH_STATEePathState.PATH_END인 항목을 삭제하려고 합니다.

Dictionary<int, Path>.Enumerator UpdateEnumerator = m_dicPath.GetEnumerator();
List<Path> listdelete = new List<Path>();

while (UpdateEnumerator.MoveNext())
{
    if (UpdateEnumerator.Current.Value.PATH_STATE == ePathState.PATH_END)
    {
        listdelete.Add(UpdateEnumerator.Current.Value);
        m_dicPath.Remove(UpdateEnumerator.Current.Value.INDEX); // 문제 발생!
    }
}

위 코드에서 while 루프 안에서 m_dicPath.Remove()를 호출하면 문제가 발생합니다. 바로 "컬렉션이 수정되었습니다. 열거 작업이 실행되지 않을 수 있습니다."라는 예외(Exception)가 발생하는 것입니다.

오류 원인

이 오류는 컬렉션을 순회하는 데 사용되는 Enumerator의 동작 방식 때문에 발생합니다. Enumerator는 컬렉션의 현재 위치를 추적하는데, 순회 도중에 컬렉션의 구조가 변경되면 (Remove 호출 등) Enumerator의 상태가 유효하지 않게 되어 오류를 발생시키는 것입니다. 마치 책갈피를 꽂아두고 책의 페이지를 중간에 찢어버리면 책갈피의 위치가 의미 없어지는 것과 같은 이치입니다.

해결 방법

이 문제를 해결하는 가장 일반적이고 효율적인 방법은 삭제할 항목들을 별도의 리스트에 저장해 두었다가, 순회가 끝난 후에 해당 리스트를 이용하여 삭제하는 것입니다.

Dictionary<int, Path>.Enumerator UpdateEnumerator = m_dicPath.GetEnumerator();

// 삭제할 항목을 저장할 리스트
List<Path> listToDelete = new List<Path>();

while (UpdateEnumerator.MoveNext())
{
    if (UpdateEnumerator.Current.Value.PATH_STATE == ePathState.PATH_END)
    {
        listToDelete.Add(UpdateEnumerator.Current.Value);
    }
}

// 순회가 끝난 후 삭제
for (int i = 0; i < listToDelete.Count; ++i)
{
    m_dicPath.Remove(listToDelete[i].INDEX);
    listToDelete[i].DestroyPath(); // 추가적인 리소스 해제 (필요한 경우)
}

listToDelete.Clear(); // 리스트 정리 (선택적)

위 코드에서는 listToDelete 리스트에 삭제할 Path 객체들을 저장한 후, 별도의 for 루프를 통해 m_dicPath에서 실제로 삭제합니다. 이렇게 하면 순회 도중에 컬렉션이 수정되지 않으므로 오류를 피할 수 있습니다. 또한, DestroyPath()를 호출하여 관련된 리소스를 정리할 수 있습니다.

개선된 코드 (C# 3.0 이상)

LINQ를 사용할 수 있다면 코드를 더 간결하게 만들 수 있습니다.

var itemsToRemove = m_dicPath.Where(kvp => kvp.Value.PATH_STATE == ePathState.PATH_END).ToList();

foreach (var item in itemsToRemove)
{
    m_dicPath.Remove(item.Key);
    item.Value.DestroyPath();
}

이 코드는 Where 메서드를 사용하여 삭제할 항목을 필터링하고, ToList()를 사용하여 리스트로 변환합니다. 이후 foreach 루프를 통해 안전하게 삭제를 진행합니다.

결론

컬렉션을 순회하면서 동시에 수정하는 것은 오류를 발생시키는 주요 원인입니다. 이 글에서 제시된 방법을 사용하여 안전하게 컬렉션을 수정하고, 더 나아가 LINQ를 활용하여 코드를 간결하게 작성할 수 있습니다.