Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intelligent way of removing items from a List<T> while enumerating in C#

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(

like image 557
John Stock Avatar asked Aug 25 '11 15:08

John Stock


People also ask

What method do you use to remove an item from a List t at a specific index?

RemoveAt (Int32) Method is used to remove the element at the specified index of the List<T>. Properties of List: It is different from the arrays.

How to remove item from List in foreach c#?

In C# one easy way is to mark the ones you wish to delete then create a new list to iterate over... list. RemoveAll(p=>p. Delete);


3 Answers

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)); 
like image 74
dlev Avatar answered Oct 21 '22 23:10

dlev


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++;
  }
}
like image 45
JaredPar Avatar answered Oct 22 '22 01:10

JaredPar


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);
        }
    }
}
like image 39
djones Avatar answered Oct 21 '22 23:10

djones