在 For Each 循环中从中删除项目时,IEnumerables 表现不同

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

众所周知,在迭代它的 For Each 循环中从 IEnumerable 中删除项目是一个非常糟糕的主意。我一直认为 IEnumerable 当您仍然尝试这样做时会抛出异常。
但是我注意到 List and TreeNodeCollection 的行为方式不同,即使它们都实现了 IListICollectionIEnumerable.

List 的行为符合我的预期。以下代码抛出 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

但是 TreeNodeCollection 不会在 .NET 2 中抛出异常并删除一些项目,在 .NET 4.5.2 中该片段会抛出 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

为什么两个 IEnumerable 之间会有如此不同的行为?

IEnumerable 界面在技术上或通过文档没有任何关于 修改 的内容。没有任何契约要求实现接口的集合在枚举期间被修改时抛出异常。

可能有些集合即使修改后也完全可以继续枚举。 IE。他们枚举的可能是"the contents of the collection as it was when enumeration started",或者他们可能对枚举开始后添加或删除的项目提供一些其他保证。

一般来说,如果您想删除项目,您应该先制作一个副本:

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

其他选项包括:

  • 使用反向 For 循环。 For i = list.Count-1 To 0 Step - 1
  • 使用 LINQ 创建新列表。 list = list.Where(...).ToList