退出循环时foreach中的集合变异:这是一种可接受的模式吗?

时间:2014-05-06 16:08:35

标签: c# exception collections foreach mutation

众所周知,迭代循环中的集合的变异是不允许的。例如,当一个项被删除时,运行时将抛出异常。

然而,今天我很惊讶地注意到,如果变异操作后面跟着任何退出循环语句,则没有异常。也就是说,循环结束。

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

我在框架代码中看到了,很明显只有当迭代器向前移动时才抛出异常。

我的问题是:上述“模式”可以被认为是一种可以接受的做法,或者在使用它时是否有任何偷偷摸摸的问题?

2 个答案:

答案 0 :(得分:2)

在你给出的例子中,回答问题“如果你在改变后破坏,在枚举期间修改集合是否是好的做法/可以接受”,这样做很好,只要你是意识到副作用

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

在大多数(如果不是全部)你可以逃脱的情况下,一个简单的if检查就足够了(正如Servy在他的回答中所做的那样),所以你可能想看看你有什么其他选择,如果你发现你自己写了很多这样的代码。

最常见的一般解决方案是添加到“kill”列表,然后在迭代后删除:

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);

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

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

答案 1 :(得分:2)

因此,对于初学者来说,你的榜样可以依赖于工作。当您要求下一个项目时,在迭代时变换集合失败。因为这可以证明在变更列表之后从未要求另一个项目,我们知道这不会发生。当然,它的工作原理并不意味着它很清楚,或者使用它是个好主意。

如果有要移除的项目,这样做是删除第二项。它设计为在没有两个项目的情况下尝试从集合中删除项目时不会中断。虽然这不是一个很好的设计方式;这让读者感到困惑,并没有有效地传达其意图。实现相同目标的一种更清晰的方法如下:

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

在更一般的情况下,foreach中的此类Remove只能用于删除一个项目,因此对于这些情况,您最好转换{{1进入forech验证是否有要删除的项目(如果需要,就像在这里一样),然后调用删除单个项目(可能涉及查询)找到要删除的项目,而不是使用硬编码索引。)