Hello, I’m getting an InvalidOperationException: Collection was modified; enumeration operation may not execute. error when I try to remove an item from a list using list.Remove(item); inside a while loop inside an IEnumerator. Here is a cut down example of my code. Please ignore how many curly brackets there are, I haven’t counted them and there may be extras.
private IEnumerator Ticker()
{
while (true)
{
foreach (var s in stats)
{
if (s.enabled)
{
foreach (var se in s.status)
{
s.status.Remove(se);
}
}
}
}
yield return new WaitForSecondsRealtime(7);
}
}
The specific problem is having Remove(se); in there, but I sorta need that. Any ideas how I can fix this? I suppose I could just cue it up to be removed in Update(), but that seems wasteful.
EDIT FOR ANYONE SEEKING THE ANSWER
You can add .ToList() to the list in the foreach however this is inefficient.
foreach (var item in list.ToList())
{
list.Remove(item);
}
It is better to use a reverse for loop. Reverse because the list drops items backwards to fill space.
for (int i = s.status.Count - 1; i >= 0; i--)
{
var se = s.status[i];
// do stuff with se if needed
s.status.RemoveAt(i);
}
The LinkedListNode is the element type in a linked list. When you add a new item to a linked list it’s a LinkedListNode being added. In same fashion a LinkedListNode is removed when removing from a linked list. Since LinkedListNode is a reference type it allocates on the heap.
To modify a list from a loop, you need to iterate the list with indexes instead of using an enumerator. The usual way is with a reverse for loop :
for (int i = s.status.Count - 1; i >= 0; i--)
{
var se = s.status[i];
// do stuff with se if needed
s.status.RemoveAt(i);
}
Also note that if you’re removing all entries, you can (should) just call s.status.Clear() after the foreach loop instead of removing each element one by one.
It’s mainly convenience. You can’t directly do a forward for loop because you would skip entries. To do a forward iteration while deleting some entries, you need to use a while loop :
int i = 0;
while (i < list.Count)
{
if (someCondition)
list.RemoveAt(i);
else
i++;
}
Why not just use List.clear()? That’s all that code above does, right? Even if that’s too fancy, list = new List<whatever>() should do the same thing, but better.
While this technically work, it’s an incredibly inefficient way of doing this, and a bad practice in general.
Not only you’re instantiating an useless new list (which is slow and is increasing GC pressure), but using Remove(item) is causing a full iteration of reference comparisons on the list, which is also very slow. Plus, it’s not safe to use if your list has duplicate entries.
Learn the good habit of using a reverse for loop and RemoveAt(index). Better write efficient and safe code the first time, that will save you a lot of time and trouble in the long run.
And as @Owen-Reynolds mentionned, if you’re removing all entries every time, just do a List.Clear() after the foreach loop instead of removing entries one by one. That is the cleanest and most performant way.