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

C# 退出循环时foreach中的集合突变:这是一种可接受的模式吗?,c#,exception,collections,foreach,mutation,C#,Exception,Collections,Foreach,Mutation,众所周知,不允许在迭代循环中对集合进行变异。例如,当一个项目被删除时,运行时将抛出一个异常 然而,今天我惊讶地注意到,如果变异操作后面跟着任何exit loop语句,则没有例外。也就是说,循环结束 //this won't throw! var coll = new List<int>(new[] { 1, 2, 3 }); foreach (var item in coll) { coll.RemoveAt(1); break; } //这不会抛出! var co

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

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

//this won't throw!
var coll = new List<int>(new[] { 1, 2, 3 });
foreach (var item in coll)
{
    coll.RemoveAt(1);
    break;
}
//这不会抛出!
var coll=新列表(新[]{1,2,3});
foreach(coll中的var项目)
{
coll.RemoveAt(1);
打破
}
我查看了框架代码,很明显,只有当迭代器向前移动时才会抛出异常


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

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

主要的副作用,也是我不建议在一般情况下这样做的原因,是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);
List killList=newlist();
foreach(以coll表示的整数i)
{
if(i<0)
增加(i);
...
}
foreach(killList中的int i)
coll.Remove(i);
有多种方法可以缩短代码,但这是最明确的方法


您还可以向后迭代,这不会导致抛出异常。这是一个很好的解决方法,但您可能希望添加一条注释,解释为什么要向后迭代。

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

主要的副作用,也是我不建议在一般情况下这样做的原因,是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);
List killList=newlist();
foreach(以coll表示的整数i)
{
if(i<0)
增加(i);
...
}
foreach(killList中的int i)
coll.Remove(i);
有多种方法可以缩短代码,但这是最明确的方法


您还可以向后迭代,这不会导致抛出异常。这是一个很好的解决方法,但您可能希望添加一条注释,解释为什么要向后迭代。

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

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

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

在更一般的情况下,foreach中这样的
Remove
只能用于删除一个项目,因此对于这些情况,最好将
forch
转换为
if
,以验证是否存在要删除的项目(如果需要,如此处所示),然后调用删除单个项目(这可能涉及查询以查找要删除的项,而不是使用硬编码索引).

因此,对于初学者来说,可以依赖您的示例来工作。在迭代过程中对集合进行变异会在您请求下一项时失败。由于这可以证明在它变异列表后不会请求另一项,我们知道这不会发生。当然,它工作的事实并不意味着它是清晰的,或者使用它是一个好主意

如果有要删除的项目,则尝试删除第二个项目。它的设计目的是在尝试从没有两个项目的集合中删除项目时不会中断。但是,这不是一种设计良好的方法;它会让读者感到困惑,并且不能有效地传达其意图。这是一种更清晰的共犯方法相同的目标如下所示:

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

在更一般的情况下,foreach中这样的
Remove
只能用于删除一个项目,因此对于这些情况,最好将
forch
转换为
if
,以验证是否存在要删除的项目(如果需要,如此处所示),然后调用删除单个项目(这可能包括一个查询来查找要删除的项目,而不是使用硬编码的索引)。

+ 1…我假设你的样本不是真的代码-考虑添加评论,比如“这个无用的代码只是演示了行为……”,以避免“你可以做更好的评论”。AlexeiLevenkov:你绝对是Rigy。