Lists and Invalid Operation Exceptions
On page 244, while discussing the List<T>
type and foreach
loops, it indicates that if you modify a list (add, remove, replace items) while doing a foreach
loop, you will get an InvalidOperationException
.
The book states that there are “two workarounds for this” and then describes only one.
This article covers the second workaround in a bit of depth.
While there are probably other workarounds than these, they are the two I used the most.
Workaround #1 is to use a for
loop instead, and then make sure that as you add and remove items from the list, you correctly maintain your index variable used in the for
loop.
Workaround #2 is this:
When you detect that you need to add or remove items to the collection, rather than modifying the collection immediately, you defer the addition or removal until after the foreach
loop. Make a note of what items need to be added or removed, and then once the foreach
loop is complete, you add all the items you needed to add and remove all the items you no longer want.
A simple way to do this is by having two other lists that contain the items to add and the items to remove:
List<Something> mainList = ...; // This is actually usually a field or a parameter or something, rather than a local variable.
List<Something> toAdd = new List<Something>();
List<Something> toRemove = new List<Something>();
foreach (Something thing in mainList)
{
if (/* detect a deletion */)
toRemove.Add(thing);
if(/* detect an addition */)
toAdd.Add(new Something());
// Do more work in the loop.
}
foreach (Something add in toAdd) // Or call `mainList.AddRange(toAdd);`
mainList.Add(add);
foreach(Something remove in toRemove)
mainList.Remove(remove);
With code like this, you are never changing a collection that you are currently iterating over, and the problem is solved.
This isn’t the only way to implement this strategy.
For example, I’ve seen some game code where the list you’re iterating over is a collection of some base class for all objects in the game, like a GameObject
class, and where this class has an IsAlive
property.
In such a scenario, you could skip the toRemove
list entirely, and when you detect the removal of the object, you set IsAlive
to false
.
(If you’re doing that, there’s a method on List<T>
called RemoveWhere
that takes a delegate (Level 36, but also look at lambdas in Level 38) that would make short work of removing all items in the list whose IsAlive
property is false
: mainList.RemoveWhere(t => !t.IsAlive);
.)