简体   繁体   中英

IEnumerables behaving different when deleting Items from them in a For Each loop

It is commonly known that removing items from an IEnumerable inside a For Each loop iterating over it is a really bad idea. I always thought that IEnumerable would throw an exception when you still try to do it.
However I noticed that List and TreeNodeCollection don't behave the same way, even though they both implement IList , ICollection and IEnumerable .

List behaves as I expected. The following code throws an InvalidOperationException :

Dim list As New List(Of String)

list.Add("Entry 1")
list.Add("Entry 2")
list.Add("Entry 3")
list.Add("Entry 4")
list.Add("Entry 5")

For Each entry As String In list
    list.Remove(entry)
Next

but TreeNodeCollection doesn't throw an exception in .NET 2 and deletes some of the Items, in .NET 4.5.2 the snippet throws a NullReferenceException :

Dim tree As New TreeView
Dim root As TreeNode = tree.Nodes.Add("Root")
root.Nodes.Add("Node 1")
root.Nodes.Add("Node 2")
root.Nodes.Add("Node 3")
root.Nodes.Add("Node 4")
root.Nodes.Add("Node 5")

For Each node As TreeNode In root.Nodes
    root.Nodes.Remove(node)
Next

Why is there such a different behaviour between two IEnumerable s?

The IEnumerable interface doesn't have anything, either technically or via documentation, to say about modifications . There is nothing contractual that requires a collection that implements the interface to throw an exception if they are modified during enumeration.

It may be that some collections are perfectly capable of continuing enumeration even after modification. Ie what they enumerate may be "the contents of the collection as it was when enumeration started", or they may offer some other guarantees about items which are added or removed after enumeration starts.

As a general rule, if you want to remove items you should make a copy first:

For Each entry As String In list.ToList()
    list.Remove(entry)
Next

Other options include:

  • Using a backwards For loop. For i = list.Count-1 To 0 Step - 1
  • Use LINQ to make a new list. list = list.Where(...).ToList

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM