当前位置: 首页 > 知识库问答 >
问题:

为什么ControlCollection不抛出Invalid术异常?

令狐宏浚
2023-03-14

针对每个循环处理跳过迭代的控件的问题,我发现允许在不断变化的集合上进行迭代:

例如,以下内容:

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:集合已修改;枚举操作可能无法执行。

然而,在a。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();
}

它跳过元素,因为迭代器运行在更改的集合上,而不会引发异常

错误?如果基础集合发生变化,迭代器是否需要抛出InvalidOPationException

所以我的问题是为什么对不断变化的ControlCollection的迭代不会抛出Invalid术异常?

附录:

IEnumerator的文档说明:

枚举器没有对集合的独占访问权;因此,枚举集合本质上不是线程安全的过程。即使集合被同步,其他线程仍然可以修改集合,这会导致枚举器抛出异常。

共有2个答案

濮彬
2023-03-14

在对foreach控件c#跳过控件的注释中也提出了同样的未引发异常的问题。该问题使用类似的代码,除了在调用Displace()之前从控件中显式删除子Control...

foreach (Control cntrl in Controls)
{
    if (cntrl.GetType() == typeof(Button))
    {
        Controls.Remove(cntrl);
        cntrl.Dispose();
    }
}

我仅通过文档就可以找到这种行为的解释。基本上,在枚举时修改任何集合都会导致抛出异常,这是一种错误的假设;这样的修改会导致未定义的行为,具体的集合类将决定如何处理该场景(如果有的话)。

根据IEnumerable的注释。GetEnumerator()和IEnumerable

如果对集合进行了更改,例如添加、修改或删除元素,则枚举器的行为未定义

类,如字典

只要集合保持不变,枚举数就仍然有效。如果对集合进行了更改,例如添加、修改或删除元素,则枚举数将不可恢复地无效,并且下一次调用MoveNext或IENumator。重置会抛出Invalid术异常

值得注意的是,正是我上面提到的每个类,而不是它们都实现的接口,通过“代码”指定了显式失败的行为。因此,它是否异常失败取决于每个类。

较旧的集合类,例如ArrayList和Hashtable,专门将此场景中的行为定义为未定义的,超出了要失效的枚举数。。。

只要集合保持不变,枚举数就保持有效。如果对集合进行了更改,例如添加、修改或删除元素,则枚举器将不可恢复地失效,并且其行为未定义。

...虽然在测试中我发现这两个类的枚举数都会在无效后抛出一个invalidooperationexception。

与上述类不同,控件。ControlCollection类既不定义也不评论此类行为,因此上述代码“仅”以微妙、不可预测的方式失败,无一例外地明确表示失败;它从未说过它会明确失败。

因此,一般来说,在枚举期间修改集合保证(可能)失败,但不保证引发异常。

莫振
2023-03-14

这个问题的答案可以在的参考源代码中找到。

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()中的注释,这些注释明确说明了这一点。

在我看来,这是一个误入歧途的“修复”,因为它通过引入一个微妙的错误来掩盖一个明显的错误(元素被悄悄地跳过,正如OP所指出的)。

 类似资料:
  • 下面是我的代码。当我运行它时,我在线程“main”java.lang.IndexOutOfBoundsException:Index:3、Size:2中得到异常,而不是我的异常消息。谁能解释一下我做错了什么,为什么会这样?谢谢!

  • 问题内容: 我陷入了这个非常奇怪的问题。在客户端中,我传递的对象是 在服务器中,我正在读取对象 我的问题是为什么我会出现EOF异常。我对对象输入流的理解是,当我调用readObject()时应该阻塞直到获得对象,这样才能知道是否达到了eof?请帮忙! 这就是我创建对象流的方式 另外,在我写完对象并刷新之后,我应该关闭流。我不会关闭它,因为对象是从代码的不同部分开始一个接一个地定期编写的。 问题答案

  • 问题内容: 我睡眠不足吗?以下代码 抛出此错误: 这绝对是行不通的!有人可以解释一下吗? 问题答案: 在规格中它说: 每当调用其origin-clean标志设置为false的canvas元素的toDataURL()方法时,该方法都必须引发SECURITY_ERR异常。 如果图像来自其他服务器,我认为您不能使用toDataURL()

  • null 为简洁起见,排除了getter和setter(用于所有字段)以及toString()。 我尽了最大的努力按照指导原则格式化代码,但它没有发生,请耐心等待。 @Entity@Table(name=“Products”)@XmlRootElement@NamedQueries({@NamedQuery(name=“Product.FindAll”,query=“从产品p中选择p”),@nam

  • 在try块中为未抛出异常子类的方法捕获异常,将无法编译。当我捕捉到异常时,它起作用了。它是如何工作的??

  • 问题内容: 直接的答案是因为s接口被指定为不会引发异常。但是为什么呢? 或换句话说:我必须依赖可以引发异常的函数。从理论上讲,这不应该发生。但是,如果发生这种情况,我希望它脱离我正在使用的整个函数(在中)。即我希望它的行为就像发生未处理的异常一样。 似乎这不可能以一种显而易见的自然方式进行(因为如果接口说它不能抛出异常,就不会)。 我该如何解决?用丑陋的try / catch并打印出异常,并希望我