107

I have the classic case of trying to remove an item from a collection while enumerating it in a loop:

List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

foreach (int i in myIntCollection)
{
    if (i == 42)
        myIntCollection.Remove(96);    // The error is here.
    if (i == 25)
        myIntCollection.Remove(42);    // The error is here.
}

At the beginning of the iteration after a change takes place, an InvalidOperationException is thrown, because enumerators don’t like when the underlying collection changes.

I need to make changes to the collection while iterating. There are many patterns that can be used to avoid this, but none of them seems to have a good solution:

  1. Do not delete inside this loop, instead keep a separate “Delete List”, that you process after the main loop.

    This is normally a good solution, but in my case, I need the item to be gone instantly as “waiting” till after the main loop to really delete the item changes the logic flow of my code.

  2. Instead of deleting the item, simply set a flag on the item and mark it as inactive. Then add the functionality of pattern 1 to clean up the list.

    This would work for all of my needs, but it means that a lot of code will have to change in order to check the inactive flag every time an item is accessed. This is far too much administration for my liking.

  3. Somehow incorporate the ideas of pattern 2 in a class that derives from List<T>. This Superlist will handle the inactive flag, the deletion of objects after the fact and also will not expose items marked as inactive to enumeration consumers. Basically, it just encapsulates all the ideas of pattern 2 (and subsequently pattern 1).

    Does a class like this exist? Does anyone have code for this? Or is there a better way?

  4. I’ve been told that accessing myIntCollection.ToArray() instead of myIntCollection will solve the problem and allow me to delete inside the loop.

    This seems like a bad design pattern to me, or maybe it’s fine?

Details:

  • The list will contain many items and I will be removing only some of them.

  • Inside the loop, I will be doing all sorts of processes, adding, removing etc., so the solution needs to be fairly generic.

  • The item that I need to delete may not be the current item in the loop. For example, I may be on item 10 of a 30 item loop and need to remove item 6 or item 26. Walking backwards through the array will no longer work because of this. ;o(

Palec
  • 12,743
  • 8
  • 69
  • 138
John Stock
  • 1,104
  • 2
  • 9
  • 11
  • Possible useful info for someone else: [Avoid Collection has been modified error](http://stackoverflow.com/q/6305228) (an encapsulation of pattern 1) – George Duckett Aug 25 '11 at 15:54
  • A side note: Lists spare much time (generally O(N), where N is the length of the list) moving the values. If efficient random access is really needed, it is possible to achieve deletes in O(log N), using a balanced binary tree holding the number of nodes in the subtree whose root it is. It is a BST whose key (the index in the sequence) is implied. – Palec Mar 01 '16 at 14:29
  • Please see the answer:http://stackoverflow.com/questions/7193294/intelligent-way-of-removing-items-from-a-listt-while-enumerating-in-c-sharp/36739055#36739055 – Dabbas Apr 20 '16 at 09:14

11 Answers11

213

The best solution is usually to use the RemoveAll() method:

myList.RemoveAll(x => x.SomeProp == "SomeValue");

Or, if you need certain elements removed:

MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));

This assume that your loop is solely intended for removal purposes, of course. If you do need to additional processing, then the best method is usually to use a for or while loop, since then you're not using an enumerator:

for (int i = myList.Count - 1; i >= 0; i--)
{
    // Do processing here, then...
    if (shouldRemoveCondition)
    {
        myList.RemoveAt(i);
    }
}

Going backwards ensures that you don't skip any elements.

Response to Edit:

If you're going to have seemingly arbitrary elements removed, the easiest method might be to just keep track of the elements you want to remove, and then remove them all at once after. Something like this:

List<int> toRemove = new List<int>();
foreach (var elem in myList)
{
    // Do some stuff

    // Check for removal
    if (needToRemoveAnElement)
    {
        toRemove.Add(elem);
    }
}

// Remove everything here
myList.RemoveAll(x => toRemove.Contains(x));
Cœur
  • 37,241
  • 25
  • 195
  • 267
dlev
  • 48,024
  • 5
  • 125
  • 132
  • 1
    Regarding your response: I need to remove the items instantly during the processing of that item not after the whole loop has been processed. The solution I'm using is to NULL any items that I want to delete instantly and delete them afterwards. This is not an ideal solution as I have to check for NULL all over the place, but it DOES work. – John Stock Aug 27 '11 at 14:34
  • Wondering If 'elem' is not an int , then we can't use RemoveAll that way it is present on the edited response code. – User M Jun 07 '19 at 19:48
30

If you must both enumerate a List<T> and remove from it then I suggest simply using a while loop instead of a foreach

var index = 0;
while (index < myList.Count) {
  if (someCondition(myList[index])) {
    myList.RemoveAt(index);
  } else {
    index++;
  }
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • This should be the accepted answer in my opinion. This allows you to consider the rest of the items in your list, without re-iterating a shortlist of items to be removed. – Slvrfn Oct 17 '18 at 03:05
20

I know this post is old, but I thought I'd share what worked for me.

Create a copy of the list for enumerating, and then in the for each loop, you can process on the copied values, and remove/add/whatever with the source list.

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}
djones
  • 2,338
  • 1
  • 20
  • 25
8

When you need to iterate through a list and might modify it during the loop then you are better off using a for loop:

for (int i = 0; i < myIntCollection.Count; i++)
{
    if (myIntCollection[i] == 42)
    {
        myIntCollection.Remove(i);
        i--;
    }
}

Of course you must be careful, for example I decrement i whenever an item is removed as otherwise we will skip entries (an alternative is to go backwards though the list).

If you have Linq then you should just use RemoveAll as dlev has suggested.

Justin
  • 84,773
  • 49
  • 224
  • 367
  • Only works when you are removing the current element though. If you are removing an arbitrary element, you will need to check whether its index was at/before or after the current index to decide whether to `--i`. – CompuChip Mar 01 '16 at 11:03
  • The original question did not make it clear that deletion of other elements than the current one must be supported, @CompuChip. This answer has not changed since it was clarified. – Palec Mar 01 '16 at 11:06
  • @Palec, I understand, hence my comment. – CompuChip Mar 01 '16 at 11:07
5

As you enumerate the list, add the one you want to KEEP to a new list. Afterward, assign the new list to the myIntCollection

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
List<int> newCollection=new List<int>(myIntCollection.Count);

foreach(int i in myIntCollection)
{
    if (i want to delete this)
        ///
    else
        newCollection.Add(i);
}
myIntCollection = newCollection;
James Curran
  • 101,701
  • 37
  • 181
  • 258
2

Let's add you code:

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

If you want to change the list while you're in a foreach, you must type .ToList()

foreach(int i in myIntCollection.ToList())
{
    if (i == 42)
       myIntCollection.Remove(96);
    if (i == 25)
       myIntCollection.Remove(42);
}
1

For those it may help, I wrote this Extension method to remove items matching the predicate and return the list of removed items.

    public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate)
    {
        IList<T> removed = new List<T>();
        for (int i = source.Count - 1; i >= 0; i--)
        {
            T item = source[i];
            if (predicate(item))
            {
                removed.Add(item);
                source.RemoveAt(i);
            }
        }
        return removed;
    }
kvb
  • 29
  • 4
0

If you're interested in high performance, you can use two lists. The following minimises garbage collection, maximises memory locality and never actually removes an item from a list, which is very inefficient if it's not the last item.

private void RemoveItems()
{
    _newList.Clear();

    foreach (var item in _list)
    {
        item.Process();
        if (!item.NeedsRemoving())
            _newList.Add(item);
    }

    var swap = _list;
    _list = _newList;
    _newList = swap;
}
Palec
  • 12,743
  • 8
  • 69
  • 138
Will Calderwood
  • 4,393
  • 3
  • 39
  • 64
0

Just figured I'll share my solution to a similar problem where i needed to remove items from a list while processing them.

So basically "foreach" that will remove the item from the list after it has been iterated.

My test:

var list = new List<TempLoopDto>();
list.Add(new TempLoopDto("Test1"));
list.Add(new TempLoopDto("Test2"));
list.Add(new TempLoopDto("Test3"));
list.Add(new TempLoopDto("Test4"));

list.PopForEach((item) =>
{
    Console.WriteLine($"Process {item.Name}");
});

Assert.That(list.Count, Is.EqualTo(0));

I solved this with a extension method "PopForEach" that will perform a action and then remove the item from the list.

public static class ListExtensions
{
    public static void PopForEach<T>(this List<T> list, Action<T> action)
    {
        var index = 0;
        while (index < list.Count) {
            action(list[index]);
            list.RemoveAt(index);
        }
    }
}

Hope this can be helpful to any one.

0

How about

int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
    myIntCollection.Remove(42); //The error is no longer here.
}
Olaf
  • 10,049
  • 8
  • 38
  • 54
  • In current C#, it can be rewritten as `foreach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); }` for any enumerable, and `List` specifically supports this method even in .NET 2.0. – Palec Mar 01 '16 at 11:11
-1

Currently you are using a list. If you could use a dictionary instead, it would be much easier. I'm making some assumptions that you are really using a class instead of just a list of ints. This would work if you had some form of unique key. In the dictionary, object can be any class you have and int would be any unique key.

    Dictionary<int, object> myIntCollection = new Dictionary<int, object>();
        myIntCollection.Add(42, "");
        myIntCollection.Add(12, "");
        myIntCollection.Add(96, "");
        myIntCollection.Add(25, "");


        foreach (int i in myIntCollection.Keys)
        {
            //Check to make sure the key wasn't already removed
            if (myIntCollection.ContainsKey(i))
            {
                if (i == 42) //You can test against the key
                    myIntCollection.Remove(96);
                if (myIntCollection[i] == 25) //or you can test against the value
                    myIntCollection.Remove(42);    
            }
        }

Or you could use

    Dictionary<myUniqueClass, bool> myCollection; //Bool is just an empty place holder

The nice thing is you can do anything you want to the underlying dictionary and the key enumerator doesn't care, but it also doesn't update with added or removed entries.

Raitpngman
  • 24
  • 3
  • 1
    Yes but this 11 year old question was specifically about Lists. – John Stock Nov 23 '22 at 17:40
  • I know it was about lists, and there are other answers related to lists. However, I've found myself in the ops situation many times, and came to realize that a dictionary would be a much better solution in some cases, so I've posted that here for others to consider as an alternative. Sometimes people get so focused on making something work they don't see an alternative or better solution. – Raitpngman Dec 06 '22 at 23:35
  • I am the OP and I specifically needed a list back then and specifically not a dictionary. This is why I didn't just say collection in the question. – John Stock Dec 07 '22 at 10:45