Search Unity

Resolved Collection was modified; enumeration operation may not execute

Discussion in 'Scripting' started by ItsMeJohnstee, Aug 26, 2020.

  1. ItsMeJohnstee

    ItsMeJohnstee

    Joined:
    Feb 18, 2020
    Posts:
    2
    Hey guys,
    I'm currently getting the error stated in the title when I run the following code:
    Code (CSharp):
    1.         foreach (GameObject enemy in GlobalVariables.enemyList)
    2.             {
    3.                 if (Vector3.Distance(transform.position, enemy.transform.position) > 4)
    4.                 {
    5.                   break;
    6.                 }
    7.  
    8.                 if (powerup)
    9.                 {
    10.                     GlobalVariables.enemyList.Remove(enemy);
    11.  
    12.                     Destroy(enemy);
    13.                 }
    14.                 else
    15.                 {
    16.                     enemy.GetComponent<Enemy>().health -= 10;
    17.                     if (enemy.GetComponent<Enemy>().health <= 0)
    18.                     {
    19.                         GlobalVariables.enemyList.Remove(enemy);
    20.                         Destroy(enemy);
    21.                     }
    22.                 }
    23.             }
    I'm aware that this is caused by the collection being altered while it's being iterated through, although I'm fairly new to arrays and lists so I'm not sure if it's fixable without rewriting the whole code.
    Help would be appreciated :) and thankyou in advance
     
  2. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    When the collection is modified (fe by Remove) the enumerator is invalidated. Easiest would be to use a plain for loop instead foreach. Or you could add the items you wish to remove to a second collection and iterate over that in a second loop and remove each element in the second collection from the first collection.

    If you use a for loop it is advisable that you iterate it from the end to the beginning. Since Remove shift items around this can also lead to bugs. Iterating from end to beginning should avoid those.
     
    ItsMeJohnstee likes this.
  3. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    There are two popular kinds of loops: for-loop and foreach-loop. Although they look similar, they are fundamentally different. A for-loop accesses the elements in an array directly, using an index, whereas a foreach-loop creates a copy of each object in the array and uses that one in the loop. However, when using classes, this is not appearent to you as a developer as classes are passed by reference and not passed by value (for more information, see What's the difference between passing by reference vs. passing by value?, for instance), which means no actual copy of the array elements are created, but only the references to those objects are copied.

    Long story short, you are not allowed to modify a collection that is currently iterated over, for instance by using a foreach-loop. A foreach-loop "pushes out" copies of every single element from your collection, one by one, so think about the inconsistencies it would create if you modified the collection while copies of elements are still being "pushed out".

    One of the easiest ways to avoid this problem is using a for-loop. In Visual Studio, if you right-click on the "foreach" keyword and look for "Quick Actions and Refactorings...", Visual Studio should actually be able to convert the loop for you, using the option "Convert to 'for'":

    Code (CSharp):
    1.         for (int i = 0; i < GlobalVariables.enemyList.Count; i++)
    2.         {
    3.             GameObject enemy = GlobalVariables.enemyList[i];
    4.             if (Vector3.Distance(transform.position, enemy.transform.position) > 4)
    5.             {
    6.                 break;
    7.             }
    8.  
    9.             if (powerup)
    10.             {
    11.                 GlobalVariables.enemyList.Remove(enemy);
    12.  
    13.                 Destroy(enemy);
    14.             }
    15.             else
    16.             {
    17.                 enemy.GetComponent<Enemy>().health -= 10;
    18.                 if (enemy.GetComponent<Enemy>().health <= 0)
    19.                 {
    20.                     GlobalVariables.enemyList.Remove(enemy);
    21.                     Destroy(enemy);
    22.                 }
    23.             }
    24.         }
    You should not, however, remove the enemy from the list while you are still using the list in the for-loop. Instead, you could, for instance, create a local list, save all enemies that should be removed in there and remove them once you are done in the for-loop. Doing this, you could also keep your foreach-loop because you no longer modify it while in the foreach-loop.

    Code (CSharp):
    1.         List<GameObject> removing = new List<GameObject>(GlobalVariables.enemyList.Count);
    2.         for (int i = 0; i < GlobalVariables.enemyList.Count; i++)
    3.         {
    4.             GameObject enemy = GlobalVariables.enemyList[i];
    5.             if (Vector3.Distance(transform.position, enemy.transform.position) > 4)
    6.             {
    7.                 break;
    8.             }
    9.  
    10.             if (powerup)
    11.             {
    12.                 removing.Add(enemy);
    13.             }
    14.             else
    15.             {
    16.                 enemy.GetComponent<Enemy>().health -= 10;
    17.                 if (enemy.GetComponent<Enemy>().health <= 0)
    18.                 {
    19.                     removing.Add(enemy);
    20.                 }
    21.             }
    22.         }
    23.  
    24.         for (int index = 0; index < removing.Count; index++)
    25.         {
    26.             GlobalVariables.enemyList.Remove(removing[index]);
    27.             Destroy(removing[index]);
    28.         }
     
    ItsMeJohnstee likes this.