tags:

views:

117

answers:

7

I have a loop that iterates through elements in a list. I am required to remove elements from this list within the loop based on certain conditions. When I try to do this in C#, I get an exception. apparently, it is not allowed to remove elements from the list which is being iterated through. The problem was observed with a foreach loop. Is there any standard way to get around this problem?

Note : One solution I could think of is to create a copy of the list solely for iteration purpose and to remove elements from the original list within the loop. I am looking for a better way of dealing with this.

+1  A: 

The recommended solution is to put all your elements you want to remove in a separate list and after the first loop, put a second loop where you iterate over the remove-list and remove those elements form the first list.

Albin Sunnanbo
Recommended by whom? This is a pointlessly expensive approach, for no benefit.
Jon Hanna
+3  A: 

You could use LINQ to replace the initial list by a new list by filtering out items:

IEnumerable<Foo> initialList = FetchList();
initialList = initialList.Where(x => SomeFilteringConditionOnElement(x));
// Now initialList will be filtered according to the condition
// The filtered elements will be subject to garbage collection

This way you don't have to worry about loops.

Darin Dimitrov
Or, if the list is a `List<T>` then you could use the `RemoveAll` method.
LukeH
@LukeH, good point.
Darin Dimitrov
+4  A: 

You can use integer indexing to remove items:

List<int> xs = new List<int> { 1, 2, 3, 4 };
for (int i = 0; i < xs.Count; ++i)
{
    // Remove even numbers.
    if (xs[i] % 2 == 0)
    {
        xs.RemoveAt(i);
        --i;
    }
}

This can be weird to read and tough to maintain, though, especially if the logic in the loop gets any more complex.

Chris Schmich
+5  A: 

If your list is an actual List<T> then you can use the built-in RemoveAll method to delete items based on a predicate:

int numberOfItemsRemoved = yourList.RemoveAll(x => ShouldThisItemBeDeleted(x));
LukeH
+1 definitely the easiest method!
Abel
+1  A: 

When using List<T>, i've found that the ToArray() method helps in this scenario vastly:

List<MyClass> items = new List<MyClass>();
foreach (MyClass item in items.ToArray())
{
    if (/* condition */) items.Remove(item);
}

The alternative is to use a for loop instead of a foreach, but then you have to decrement the index variable whenever you remove an element.

Bradley Smith
If you're using a `List<T>` then why not use the built-in `RemoveAll` method for this scenario?
LukeH
I shy away from using methods that take delegates as their parameter, because you either have to create a new method or add an anonymous method to the current block. If you do this, your code becomes incompatible with edit-and-continue when debugging.
Bradley Smith
Why the needless copy operation in ToArray()?
Jon Hanna
Actually, i've been thinking about this one. Since the generic List class is implemented using arrays, every call to Remove() will carry a large overhead with it. It would actually be more efficient to declare a new List (whose initial capacity was set to the item count in the first List), add every element that doesn't match the removal condition into the new List and then assign the new List to the variable containing the old List.
Bradley Smith
A: 

The reason you get an error is because you're using a foreach loop. If you think about how a foreach loop works this makes sense. The foreach loop calls the GetEnumerator method on the List. If you where to change the number of elements in the List, the Enumerator the foreach loop holds wouldn't have the correct number of elements. If you removed an element a null exception error would be thrown, and if you added an element the loop would miss an item.

If you like Linq and Lamda expressions I would recommend Darin Dimitrov solution, otherwise I would use the solution provided by Chris Schmich.

TheLukeMcCarthy
A: 

Another trick is to loop through the list backwards.. removing an item won't affect any of the items you are going to encounter in the rest of the loop.

I'm not recommending this or anything else though. Everything you need this for can probably be done using LINQ statements to filter the list on your requirements.

clocKwize