InvalidOperationException: Collection was modified; enumeration operation may not execute
InvalidOperationException: Collection was modified; enumeration operation may not execute.
A foreach loop was walking through a list, dictionary or set, and something added or removed an item before the loop finished. C# stops the loop rather than risk skipping or repeating items.
Read the Console line first
The stack trace shows the foreach line at the bottom of your code and, above it, the method that changed the collection. That second method is often in another script, called from inside the loop.
Common causes
Removing items inside the foreach
The direct case: the loop body calls list.Remove(item) on the list it is iterating.
ExampleC#foreach (var enemy in enemies) { if (enemy.IsDead) enemies.Remove(enemy); // throws on the next step }An event handler changes the list
The loop calls enemy.TakeDamage, the enemy dies, and its OnDeath handler removes it from the same list the loop is walking.
OnDisable or OnDestroy removes the object
A manager loops over registered objects and destroys or disables one; that object's OnDisable unregisters it from the list mid-loop.
The fix
// Remove in a backwards for loop
for (int i = enemies.Count - 1; i >= 0; i--)
{
if (enemies[i].IsDead)
enemies.RemoveAt(i);
}
// Or remove everything that matches in one call
enemies.RemoveAll(e => e.IsDead);
// Or loop over a copy when the body may change the list
foreach (var enemy in enemies.ToArray())
enemy.TakeDamage(10);Stop it coming back
Never change a collection inside a foreach over it. When callbacks might change it, loop over a copy, or collect the changes in a second list and apply them after the loop.
Questions
- Does ToArray or ToList cost much?
- It allocates a copy each time, which matters in a loop that runs every frame over many items. For per-frame loops, prefer a backwards for loop or a separate list of pending removals.
- Can a for loop going forwards work?
- Only if you adjust the index after removing (
i--), which is easy to get wrong. Going backwards avoids the problem entirely. - Does the same apply to dictionaries?
- Yes. Changing a dictionary while looping over it, or over its Keys or Values, throws the same exception. Loop over a copy of the keys, such as
new List<string>(dict.Keys).
Scripts that avoid it
Related errors
Got a different error? Paste it into the error lookup.