-1

It happens so often to me that I need to remove items from a List(Of) if a certain condition is met.

I prefer to do it this way:

For Each nClass As SomeClass In MyList
    If (Something) Then
        MyList.Remove(nClass)
    End If
Next 

However, when I remove an item, the collection is changed, and the For Next Statement can't proceed, and a System.InvalidOperationException is thrown.

I wonder if there's any general way to do this properly without writing big workarounds.

Can anybody tell how this should be done correctly?

I'm attaching a test code to see the error:

Public Class Form1

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    Dim nList As New List(Of SomeClass)

    For i As Integer = 0 To 5
        Dim nNewItem As New SomeClass
        nNewItem.Int = i
        nNewItem.Text = "My text " + i.ToString
        nList.Add(nNewItem)
    Next

    For Each nItem As SomeClass In nList
        If nItem.Int > 1 And nItem.Int < 5 Then
            nList.Remove(nItem)
        End If
    Next

End Sub

End Class

Public Class SomeClass
Private _iInt As Integer = 0
Private _sText As String = String.Empty

Public Property Int() As Integer
    Get
        Return _iInt
    End Get
    Set(value As Integer)
        _iInt = value
    End Set
End Property

Public Property Text() As String
    Get
        Return _sText
    End Get
    Set(value As String)
        _sText = value
    End Set
End Property
End Class
tmighty
  • 10,734
  • 21
  • 104
  • 218
  • Did you try to clone you MyList and loop through the clone, removing from MyList? If yes, make your loop from the last item in the list to the first, for example `For x As Integer = MyList.Count - 1 To 0 Step -1` – muffi Sep 05 '17 at 12:09
  • Possible duplicate of [Removing Items in a List While Iterating Through It with For Each Loop](https://stackoverflow.com/questions/19252285/removing-items-in-a-list-while-iterating-through-it-with-for-each-loop) – A Friend Sep 05 '17 at 12:37
  • You have to loop backwards in such cases, The enumerator is invalidated once a collection is modified – Ňɏssa Pøngjǣrdenlarp Sep 05 '17 at 13:10

1 Answers1

1

As Plutonix said, you need to work backwards through your loop -

Change

For Each nItem As SomeClass In nList
    If nItem.Int > 1 And nItem.Int < 5 Then
        nList.Remove(nItem)
    End If
Next

to

For I As Integer = nList.Count to 0 Step -1
    If nlist(I).Int > 1 And nList(I).Int < 5 Then
        nList.RemoveAt(I)
    End If
Next

The reason being that in a For Each..Next loop, the number of items in the object is recorded at the start of the loop and does not change. Even if you add or remove items in the object.

For example. Say you start off with a list of 10 letters.

A

B

C

D

E

F

G

H

I

J

So. At the start of your For Each loop, the number of items is recorded (10). As the loop iterates through the list, let's say you remove "E".

For the purpose of this explanation is doesn't matter too much which one. Anyway, you'll end up with all the subsequent items being moved back one. So what was at index position 5 will be at position 4 and so on to what was at index 10 is now at index 9 . The loop carries on and when it tries to access the item at index 10, it doesn't exist because it's outside the bounds of the list that now has 9 items.

If instead you use a `For..Next' loop stepping backwards, and start at the list item and head backwards, when you get to say, "E" and want to remove it, and then step backwards again and again until you get to the first item,the first item is still at the same index position, so no problem.

David Wilson
  • 4,369
  • 3
  • 18
  • 31
  • But why does the enumerator go berzek just because I one item that it has already processed? – tmighty Sep 05 '17 at 14:31
  • I don't understand why I have to work backwards when I use RemoveAt. – tmighty Sep 06 '17 at 06:41
  • In your code, when the loop first starts, the number of items is stored internally by your program. If you remove an item the original count that the program stores is not updated. So, If you have a list of 10 items, your `For Each` loop will store 10 and will loop that many times. However, if you remove an item from the list in that loop, the number of items will then be 9, but your `For..Each` loop will still think that there are 10 items to process and when it tries to process number 10, it wont exist. – David Wilson Sep 06 '17 at 17:13
  • You have to work backwards because if you start at item 10 and say, you remove item 9, the next item in the loop will be item 9, item 8 and so on to 0. At no point will there be a nonexistant item. Hope that all helps – David Wilson Sep 06 '17 at 17:15