C# Dictionary 순회 중 삭제 시 발생하는 오류 및 해결 방법
C#에서 Dictionary
와 같은 컬렉션을 사용할 때, 특정 조건에 따라 요소를 삭제해야 하는 경우가 발생합니다. 이때 흔히 저지르는 실수가 순회(iteration) 도중에 바로 삭제를 시도하는 것입니다. 이 글에서는 이러한 경우 발생하는 오류를 설명하고, 올바른 해결 방법을 제시합니다.
문제 상황
다음과 같은 코드를 생각해 보겠습니다. m_dicPath
는 Dictionary<int, Path>
타입의 딕셔너리이고, Path
객체의 PATH_STATE
가 ePathState.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를 활용하여 코드를 간결하게 작성할 수 있습니다.