내일배움캠프 TIL

내일배움캠프 64일차 TIL "foreach문 중의 수정"

Jooglorystar 2024. 12. 17. 20:30

 

건축 시스템을 개발하면서, 씬간 이동시 건설된 오브젝트를 비활성화하고, 다시 배치하는 것을 구현 하던중 오류가 발생했다.

 

 

// 건물 배치시 Dictionary에 추가되는 부분
_buildingDataDictionary.Add(_objectToBuild.area.position, _objectToBuild);

// 건물 회수
private void ReleaseBuilding(GameObject p_building)
{
    GameManager.Instance.PoolM.ReleaseObject(PoolTag.Building, p_building);
}    

// 복수 건물 회수
public void ReleaseBuildings()
{
    foreach(var (buildingPosition, buildingData) in _buildingDataDictionary)
    {
        ReleaseBuilding(buildingData.gameObject);
    }
}

// 건물 재배치 메서드
public void LoadBuildings()
{
    foreach (var (buildingPosition, buildingData) in _buildingDataDictionary)
    {
        Building building = GameManager.Instance.PoolM.GetObject(PoolTag.Building).GetComponent<Building>();
        _buildingDataDictionary[buildingPosition] = building;
        building.SetBuilingData(buildingData.buildingData);
        building.transform.position = buildingPosition;
    }
}

 

기본적으로 건물을 배치할 때, _buildingDataDictionary에 좌표와 Building 을 저장하고, 

이전에 사용한 CropLoader에서 씬 비활성화시 Release하고 활성화시 다시 Get 하는 것을 생각했었다.

 

당연히 이 메서드에서 예외 오류가 발생했다. 

원인은 LoadBuildings()였다.

 

// 건물 재배치 메서드
public void LoadBuildings()
{
    foreach (var (buildingPosition, buildingData) in _buildingDataDictionary)
    {
        Building building = GameManager.Instance.PoolM.GetObject(PoolTag.Building).GetComponent<Building>();
        _buildingDataDictionary[buildingPosition] = building;
        building.SetBuilingData(buildingData.buildingData);
        building.transform.position = buildingPosition;
    }
}

 

우선 오류 코드는 다음과 같았다.

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

 

 

foreach문을 사용하던 도중 중간에 _buildingDataDictionary값을 변경한 것이 오류의 원인이었다.

 

foreach문은 반복하기 전에 그 콜렉션의 내부 내용을 저장하고, 반복문을 실행한다.

그리고 내부 정보가 변경되면 예외가 발생하게 되어있다.

 

왜냐하면 순회중 값이 추가되거나, 수정이 된다면, 순회가 불안정 해질 수 있었기에 순회중 값 수정을 막은 것이다.

 

처음 코드에서 굳이 foreach에서 값을 수정하려고 했던 이유는, ReleaseBuildings 부분이 제대로 작동하지 않았기 때문이다. 처음에는 비활성이 제대로 되지만, 다시 로드될 경우, 그 오브젝트는 _buildingDataDictionary에 저장된 Building과는 다른 Building이고 오브젝트 였기에 두번째부턴 제대로 비활성화가 되지 않았다.

그래서 해당 딕셔너리를 수정하려고 했던 것이다.

 

그래서 아예 정보와 건물을 따로 저장하는 것이 나을 것 같았기에 다음과 같이 수정하였다.

 

// 설치된 건물의 정보와 설치된 건물 오브젝트를 분리
private Dictionary<Vector3Int, BuildingSO> _buildingDataDictionary = new Dictionary<Vector3Int, BuildingSO>();
private List<Building> _buildings = new List<Building>();

// 설치 될 때 각각 추가해주는 부분
_buildingDataDictionary.Add(_objectToBuild.area.position, _objectToBuild.buildingData);
_buildings.Add(_objectToBuild);


private void ReleaseBuilding(GameObject p_building)
{
    GameManager.Instance.PoolM.ReleaseObject(PoolTag.Building, p_building);
}    

// 건물 리스트를 통해 비활성화 및 리스트 초기화
public void ReleaseBuildings()
{
    foreach(Building building in _buildings)
    {
        ReleaseBuilding(building.gameObject);
    }
    _buildings.Clear();
}

// Load시 건물 오브젝트를 리스트에 다시 추가
public void LoadBuildings()
{
    foreach (var (buildingPosition, buildingData) in _buildingDataDictionary)
    {
        Building building = GameManager.Instance.PoolM.GetObject(PoolTag.Building).GetComponent<Building>();
        _buildings.Add(building);
        building.SetBuilingData(buildingData);
        building.transform.position = buildingPosition;
    }
}

 

위 코드대로 수정하니 문제 없이 동작할 수 있었다.