Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

.net - Remove Items from Collection in a loop (Combo Box items)

I am trying to remove all items from a combo box list that begin with "~$".

For Each item As String In cstmcmbxDocs.Items
    If Not cstmcmbxDocs.Items.Contains("~$") Then
    Dim STR As Int16 = cstmcmbxDocs.FindString("~$")
    'gets the index of the item which contains "~$"
    cstmcmbxDocs.Items.RemoveAt([STR])
    End If
Next

But it only returns one instance which it removes. It doesn't continue searching - what am i missing here? (Note that I am using 3.5 and not 4+ because it needs to be compatible with XP)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You cannot modify a collection (add or delete) as you iterate using For Each/Next. Nor can you use a standard For n As Int32 loop. Instead:

For n As Int32 = cstmcmbxDocs.Items.Count -1 To 0 Step -1
    ...
    If [your criteria here] Then
        cstmcmbxDocs.Items.RemoveAt(n)   ' remove current one
    End If
Next n

You should also turn on Option Strict: Dim STR As Int16 = cstmcmbxDocs.FindString("~$") wont compile. And you do not need to use FindString - your loop is already going to look at each item, so just examine each for the condition.


Why a For Each loop fails

For Each item As String In cstmcmbxDocs.Items

Removing from inside this type of loop should result in an InvalidOperationException. If you click the Get general help link you find this:

IEnumerator.MoveNext

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 the next call to MoveNext or Reset throws an InvalidOperationException.


Why a forward For n loop will fail

Consider:

Dim lst As New List(Of String)
lst.AddRange(New String() {"Alpha", "Beta", "Gamma", "Delta", "Echo"})

For n As Int32 = 0 To lst.Count - 1
    If lst(n) = "Gamma" OrElse lst(n) = "Delta" Then
        lst.RemoveAt(n)
    End If
Next

enter image description here

  • When (n=2) "Gamma" is the current item.
  • Your code RemoveAt(n) so everything moves up one. n now points to "Delta"
  • Then Next increments n so it now points to "Echo"

"Delta" is never evaluated in the loop because it is skipped. Every element after a deleted one will be skipped. This is what MSDN means by enumerator is irrecoverably invalidated.

In this case, your loop will also run out of items! The end loop value, lst.Count - 1 is calculated only at the start of the loop. Removing items reduces the actual size so it will eventually crash with an ArgumentOutOfRangeException. As before, loop backwards using For n:

For n As Int32 = lst.Count - 1 To 0 Step - 1
    If lst(n) = "Gamma" OrElse lst(n) = "Delta" Then
        lst.RemoveAt(n)
    End If
Next

Now, when items are removed, the part of the list which is reordered is the part the code has already tested.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...