关注这个问题Foreach loop for disposing controls skipping iterations它告诉我,在更改的集合中允许迭代:
例如,以下内容:
List<Control> items = new List<Control>
{
new TextBox {Text = "A", Top = 10},
new TextBox {Text = "B", Top = 20},
new TextBox {Text = "C", Top = 30},
new TextBox {Text = "D", Top = 40},
};
foreach (var item in items)
{
items.Remove(item);
}
抛出
InvalidOperationException:修改了集合;枚举操作可能无法执行。
但是在.Net表单中,您可以这样做:
this.Controls.Add(new TextBox {Text = "A", Top = 10});
this.Controls.Add(new TextBox {Text = "B", Top = 30});
this.Controls.Add(new TextBox {Text = "C", Top = 50});
this.Controls.Add(new TextBox {Text = "D", Top = 70});
foreach (Control control in this.Controls)
{
control.Dispose();
}
跳过元素,因为迭代器在更改的集合上运行,而不会抛出异常
错误?如果底层集合发生变化,则抛出InvalidOperationException
所需的迭代器不是?
所以我的问题是为什么迭代更改ControlCollection
NOT抛出InvalidOperationException?
附录:
documentation for IEnumerator
说:
枚举器没有对集合的独占访问权限;因此,枚举通过集合本质上不是一个线程安全的过程。即使集合是同步的,其他线程仍然可以修改集合,会导致枚举器抛出异常。
答案 0 :(得分:9)
可以在the Reference Source for ControlCollectionEnumerator
private class ControlCollectionEnumerator : IEnumerator {
private ControlCollection controls;
private int current;
private int originalCount;
public ControlCollectionEnumerator(ControlCollection controls) {
this.controls = controls;
this.originalCount = controls.Count;
current = -1;
}
public bool MoveNext() {
// VSWhidbey 448276
// We have to use Controls.Count here because someone could have deleted
// an item from the array.
//
// this can happen if someone does:
// foreach (Control c in Controls) { c.Dispose(); }
//
// We also dont want to iterate past the original size of the collection
//
// this can happen if someone does
// foreach (Control c in Controls) { c.Controls.Add(new Label()); }
if (current < controls.Count - 1 && current < originalCount - 1) {
current++;
return true;
}
else {
return false;
}
}
public void Reset() {
current = -1;
}
public object Current {
get {
if (current == -1) {
return null;
}
else {
return controls[current];
}
}
}
}
特别注意MoveNext()
中明确解决此问题的评论。
IMO这是一个被误导的“修复”,因为它通过引入一个微妙的错误掩盖了一个明显的错误(如OP所述,元素被默默地跳过)。
答案 1 :(得分:3)
在foreach control c# skipping controls的注释中引发了抛出异常 not 的同一问题。该问题使用类似的代码,除了在调用Control
之前将子Controls
从Dispose()
中显式删除了。
foreach (Control cntrl in Controls)
{
if (cntrl.GetType() == typeof(Button))
{
Controls.Remove(cntrl);
cntrl.Dispose();
}
}
我仅通过文档就能找到有关此行为的解释。基本上,在枚举时修改 any 集合总是会引发异常是一个错误的假设;这样的修改会导致未定义的行为,具体取决于集合类如何处理这种情况。
根据IEnumerable.GetEnumerator()
和IEnumerable<>.GetEnumerator()
方法的说明...
如果对集合进行了更改(例如添加,修改或删除元素),则枚举器的行为是不确定的。
据记录,诸如Dictionary<>
,List<>
和Queue<>
之类的类在枚举期间被修改时会抛出InvalidOperationException
。
只要集合保持不变,枚举数将保持有效。如果对集合进行了更改(例如添加,修改或删除元素),则枚举数将不可避免地无效,并且下次调用MoveNext或IEnumerator.Reset会引发InvalidOperationException。
值得一提的是,上面提到的每个类(而不是它们都实现的接口)指定了通过InvalidOperationException
进行显式失败的行为。因此,是否因异常而失败取决于每个类。
诸如ArrayList
和Hashtable
之类的较旧的收集类专门将这种情况下的行为定义为未定义,超出了使枚举器无效的范围……
只要集合保持不变,枚举数将保持有效。如果对集合进行了更改(例如添加,修改或删除元素),则枚举数将无法恢复,并且其行为是不确定的。
...尽管在测试中,我发现两个类的枚举数实际上在无效后都会抛出InvalidOperationException
。
与上述类不同,Control.ControlCollection
class既没有定义也没有注释这种行为,因此上述代码“仅”以一种微妙的,不可预测的方式失败,没有任何异常明确表明失败; 从没说过会明确失败。
因此,通常,在枚举期间修改集合肯定会(很可能)失败,但不会保证会引发异常。