views:

584

answers:

4

Hi,

Basically, I would like to remove an item from a list whilst inside the foreach loop. I know that this is possible when using a for loop, but for other purposes, I would like to know if this is achievable using a foreach loop.

In python we can achieve this by doing the following:

a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

for i in a:
    print i

    if i == 1:
        a.pop(1)

This gives the following Output

>>>1
3
4
5
6
7
8
9

But when doing something similar in c#, I get an InvalidOperationException, I was wondering if there was a way of getting around this, without just simply using a for loop.

The code in c# that I used when the exception was thrown:

static void Main(string[] args)
  {
  List<string> MyList = new List<string>(new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9"});

  foreach (string Item in MyList)
    {
    if (MyList.IndexOf(Item) == 0)
      {
      MyList.RemoveAt(1);
      }

    Console.WriteLine(Item);
    }
  }

Thanks in advance

+18  A: 

You can't do this. From the docs for IEnumerator<T>:

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.

Alternatives are:

  • Build up a new list of items to remove, then remove them all afterwards
  • Use a normal "for" loop and make sure you're careful about not going over the same element twice or missing any out. (You've said you don't want to do this, but what you're trying to do just won't work.)
  • Build a new collection containing only the elements you want to retain

The last of these alternatives is the LINQ-like solution, where you'd typically write:

var newList = oldList.Where(x => ShouldBeRetained(x)).ToList();

(Where ShouldBeRetained is whatever logic you want, of course.) The call to ToList() is only necessary if you actually want it in a list. This leads to more declarative code which is often easier to read. I can't easily guess what your original loop is meant to do (it seems pretty odd at the moment) whereas if you can express the logic purely in terms of the item, it can be a lot clearer.

Jon Skeet
I was mainly wondering if I had missed something somewhere with the foreach instance, but if it's not achievable, at least it's now confirmed! Thanks for the answer
ThePower
A: 

You definitely may not change a collection in any way when using a foreach loop on it.

You can use a for loop and manage the index for yourself or make a copy of the collection and as you are looping the original, remove items from the copy that equal the item in the original.

In both cases it's not quite as clear or convenient :).

Robert
A: 

Why would you want to do that? Your code can be achieved with:

a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
a.pop(1)

The loop is completely pointless.

Yes, you can in Python in general modify the things you loop over. It's usually a bad idea. Consider this more reasonable code:

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for x in a:
...   print(x)
...   a.remove(x)
... 
1
3
5
7
9
>>> print(a)
[2, 4, 6, 8]

This is typically not what you wanted to do. You wanted to do something for everything in a, and then delete it. This would be a better solution.

>>> while a:
...   print(a.pop())
... 
9
8
7
6
5
4
3
2
1
>>> print(a)
[]

So can you do it in C#? No. But the point is that you shouldn't do it in Python either.

Lennart Regebro
The loop was just a very basic example, there's obviously more work behind it, but I'm not going to post all of that on here..
ThePower
if i == 1 - is not checking an index but a value, therefor two different lists could yield different results. Not pointless. In any case, I imagine the example is a simple case of a collision of some sort in which it may be required to remove an item if another item is present in the list. Though this may be easier to detect on insertion.
kjfletch
But a.pop(1) uses an index, and not a value, so it will remove the second value, no matter what the values are.
Lennart Regebro
+3  A: 

If all you need is to remove all items that satisfy a condition you could use the List<T>.RemoveAll method:

List<string> MyList = new List<string>(new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" });
MyList.RemoveAll(item => item == "1");

Note that this modifies the initial list.

Darin Dimitrov