为什么foreach on PlaceHolder.Controls会抛出“Collection was modified”?

时间:2014-02-08 01:19:26

标签: c# asp.net exception foreach

protected override void Render(HtmlTextWriter writer)
{
    HtmlGenericControl div1 = new HtmlGenericControl("div"); 
    div1.Attributes.Add("class", "modalbox");
    if (!ClientVisible)
        div1.Attributes.CssStyle.Add("display", "none");
    HtmlGenericControl div2 = new HtmlGenericControl("div");
    div2.Attributes.Add("class", "modalbox-m1");
    HtmlGenericControl div3 = new HtmlGenericControl("div");
    div3.Attributes.Add("class", "modalbox-m2");
    foreach (Control c in this.Controls)
        div3.Controls.Add(c); // exception here 
    div2.Controls.Add(div3);
    div1.Controls.Add(div2);
    div1.RenderControl(writer);
}

我无法理解WHO和WHERE更改this.Controls

3 个答案:

答案 0 :(得分:2)

问题在于,cControl只有一个Parent ,并且在添加控件时建立了双向关系一个ControlCollection。除非某些内容被破坏,否则相同的Control 将不会存在于多个ControlCollections中。

因此,当c添加到div3时,它首先来自前一个父容器的removes itself - 在这种情况下是c.Parent.Controls,与{{1}相同的对象正在迭代的。最终结果是修改异常,因为将控件添加到不同的集合会将其从迭代集合中删除..

避免此异常的最简单方法是创建集合的副本,与IEnumerable.ToList一样。在删除控件时,不会修改独立于ControlCollection的新列表。

this.Controls

但是,在// Note the "ToList()", from LINQ/Enumerable foreach (Control c in this.Controls.ToList()) { div3.Controls.Add(c); // exception here } 内添加控件“相当可疑”并且在很多情况下它会在回发中打破控件。


如果我只想将控件集合包装在代码中并且标记不会影响控件树,那么我将使用类似下面的内容来生成适当的渲染输出而不用< / em>创建新控件或以其他方式修改控件树。 HtmlTextWriter提供了生成相关标记的所有基本方法,即使它可能很乏味。

Render

提示:RenderBeginTag / RenderEndTag作为堆栈工作,通过AddAttribute设置的属性始终适用于下一个RenderBeginTag。


在实践中,我强烈推荐(并经常使用)User Controls,它允许ASCX标记并创建控制树声明。

答案 1 :(得分:1)

通过将this.controls中的控件添加到div3集合,您的代码正在改变它。

我不知道确切的机制,但我怀疑此操作导致c的重新托管,这会影响this.controls

解决方案是将其更改为for循环,该循环通过this.controls向后工作:

        for (int i = this.Controls.Count - 1; i >= 0; i--) {
            Control c = this.Controls[i];
            div3.Controls.Add(c);
        }

答案 2 :(得分:1)

我的最终解决方案:

protected override void CreateChildControls()
{
    HtmlGenericControl div1 = new HtmlGenericControl("div");
    div1.Attributes.Add("class", "modalbox");
    if (!ClientVisible)
        div1.Attributes.CssStyle.Add("display", "none");
    HtmlGenericControl div2 = new HtmlGenericControl("div");
    div2.Attributes.Add("class", "modalbox-m1");
    HtmlGenericControl div3 = new HtmlGenericControl("div");
    div3.Attributes.Add("class", "modalbox-m2");
    div1.Controls.Add(div2);
    div2.Controls.Add(div3);
    while (Controls.Count != 0)
        div3.Controls.Add(Controls[0]);
    Controls.Add(div1);
}