2

众所周知,不允许在迭代循环中对集合进行突变。例如,当删除某个项目时,运行时将引发异常。

然而,今天我惊讶地发现,如果变异操作后跟任何退出循环语句,也不例外。即,循环结束。

//this won't throw!
var coll = new List<int>(new[] { 1, 2, 3 });
foreach (var item in coll)
{
    coll.RemoveAt(1);
    break;
}

我查看了框架代码,很明显只有当迭代器向前移动时才会引发异常。

我的问题是:上面的“模式”可以被认为是一种可接受的做法,或者使用它有什么偷偷摸摸的问题吗?

4

2 回答 2

2

因此,对于初学者来说,可以依靠您的示例来工作。当你去请求下一个项目时,在迭代时改变一个集合会失败。由于这可证明在它改变列表后永远不会要求另一个项目,我们知道这不会发生。当然,它工作的事实并不意味着它很清楚,或者使用它是一个好主意。

如果有要删除的项目,这试图做的是删除第二个项目。它旨在在尝试从没有两个项目的集合中删除项目时不会中断。不过,这不是一种精心设计的方式;它使读者感到困惑,并且不能有效地传达其意图。实现相同目标的更清晰的方法如下所示:

if(coll.Count > 1)
    coll.RemoveAt(1);

在更一般的情况下,Removeforeach 中的这种 a 只能用于删除一个项目,因此对于这些情况,您最好将其forech转换为if验证是否有要删除的项目(如果需要,因为它是此处),然后调用删除该单个项目(这可能涉及查找要删除的项目的查询,而不是使用硬编码索引)。

于 2014-05-06T16:23:32.203 回答
2

在您给出的示例中,并回答“如果您在更改后中断,在枚举期间修改集合是否是好的做法/可以接受”的问题,只要您知道副作用就可以这样做.

主要的副作用,以及为什么我不建议在一般情况下这样做,是你的 foreach 循环的其余部分不会执行。在我使用过的几乎每个 foreach 实例中,我都会认为这是一个问题。

在大多数(如果不是全部)情况下,您可以侥幸逃脱,一个简单的 if 检查就足够了(正如 Servy 在他的回答中所说的那样),所以如果您发现自己在写这篇文章,您可能想看看还有哪些其他可用的选项种代码很多。

最常见的通用解决方案是添加到“杀死”列表,然后在迭代后删除:

List<int> killList = new List<int>();
foreach (int i in coll)
{
   if (i < 0)
      killList.Add(i);

    ...
}

foreach (int i in killList)
    coll.Remove(i);

有多种方法可以缩短这段代码,但这是最明确的方法。

您也可以向后迭代,这不会导致抛出异常。这是一个巧妙的解决方法,但您可能需要添加注释来解释为什么要向后迭代。

于 2014-05-06T16:15:18.727 回答