天道酬勤,学无止境

Why does ControlCollection NOT throw InvalidOperationException?

Following this question Foreach loop for disposing controls skipping iterations it bugged me that iteration was allowed over a changing collection:

For example, the following:

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

throws

InvalidOperationException: Collection was modified; enumeration operation may not execute.

However in a .Net Form you can do:

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

which skips elements because the the iterator runs over a changing collection, without throwing an exception

bug? aren't iterators required to throw InvalidOperationException if the underlaying collection changes?

So my question is Why does iteration over a changing ControlCollection NOT throw InvalidOperationException?

Addendum:

The documentation for IEnumerator says:

The enumerator does not have exclusive access to the collection; therefore, enumerating through a collection is intrinsically not a thread-safe procedure. Even when a collection is synchronized, other threads can still modify the collection, which causes the enumerator to throw an exception.

评论

The answer to this can be found in 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];
            }
        }
    }
}

Pay particular attention to the comments in MoveNext() which explicitly address this.

IMO this is a misguided "fix" because it masks an obvious error by introducing a subtle one (elements are silently skipped, as noted by the OP).

This same issue of an exception not being thrown was raised in the comments on foreach control c# skipping controls. That question uses similar code except the child Control is explicitly removed from Controls before calling Dispose()...

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

I was able to find an explanation for this behavior through documentation alone. Basically, that modifying any collection while enumerating always causes an exception to be thrown is an incorrect assumption; such a modification causes undefined behavior, and it's up to the specific collection class how to handle that scenario, if at all.

According to the remarks for the IEnumerable.GetEnumerator() and IEnumerable<>.GetEnumerator() methods...

If changes are made to the collection, such as adding, modifying, or deleting elements, the behavior of the enumerator is undefined.

Classes such as Dictionary<>, List<>, and Queue<> are documented to throw an InvalidOperationException when modified during enumeration...

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to MoveNext or IEnumerator.Reset throws an InvalidOperationException.

It's worth calling attention to the fact that it's each class I mentioned above, not the interfaces they all implement, that specifies the behavior of explicit failure via an InvalidOperationException. Thus, it's up to each class whether it fails with an exception or not.

Older collection classes such as ArrayList and Hashtable specifically define the behavior in this scenario as undefined beyond the enumerator being invalidated...

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.

...although in testing I found that enumerators for both classes do, in fact, throw an InvalidOperationException after being invalidated.

Unlike the above classes, the Control.ControlCollection class neither defines nor comments on such behavior, hence the above code failing in "merely" a subtle, unpredictable way with no exception explicitly indicating failure; it never said it would explicitly fail.

So, in general, modifying a collection during enumeration is guaranteed to (likely) fail, but not guaranteed to throw an exception.

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 如何找到以前的活动控件C#(How to find previous active control c#)
    问题 我正在开发键盘控件,非常简单地嵌入到窗体中。 使用sendkey类执行char输入。 要使此功能起作用,需要知道先前选择的控件。 回答1 像下面这样的东西应该可以解决问题: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace DragDropTest { public partial class LostFocusTestForm : Form { private Control _lastControl; public LostFocusTestForm() { InitializeComponent(); TrapLostFocusOnChildControls(this.Controls); } private void finalTextBox_Enter(object sender, EventArgs e) { MessageBox.Show("From " + _lastControl.Name + " to " + this
  • Using C# to recursively get a collection of controls from a controlcollection
    Currently I am trying to extract a collection of dynamically created controls (checkboxes and dropdownlists) from a recursive control collection (repeater). This is the code I am using. private void GetControlList<T>(ControlCollection controlCollection, ref List<T> resultCollection) { foreach (Control control in controlCollection) { if (control.GetType() == typeof(T)) resultCollection.Add((T)control); if (control.HasControls()) GetControlList(controlCollection, ref resultCollection); } } I am having problems with the following line: resultCollection.Add((T)control); I get the error ... Cannot
  • 为什么 Controls 集合不提供所有 IEnumerable 方法?(Why doesn't the Controls collection provide all of the IEnumerable methods?)
    问题 我不确定 ASP.Net 的 ControlCollection 是如何工作的,所以也许有人可以为我解释一下。 我最近发现了扩展方法和 Linq 的神奇之处。 好吧,我很悲哀地发现这是无效的语法 var c=Controls.Where(x => x.ID=="Some ID").SingleOrDefault(); 但是,据我所知, Controls确实实现了提供此类方法的IEnumerable接口,那么有什么作用呢? 为什么这不起作用? 我至少找到了解决这个问题的体面的工作: var list = (IEnumerable<Control>)Controls; var this_item = list.Where(x => x.ID == "Some ID").SingleOrDefault(); 回答1 不, IEnumerable上没有很多扩展方法: IEnumerable<T>有。 它们是两个独立的接口,尽管IEnumerable<T>扩展了IEnumerable 。 正常的 LINQ 转换方式是使用 Cast<T>() 和 OfType<T>() 扩展方法,它们确实扩展了非泛型接口: IEnumerable<TextBox> textBoxes = Controls.OfType<TextBox>(); IEnumerable<Control> controls
  • 使用C#中的按钮清除多个文本框(Clear multiple text boxes with a button in C#)
    问题 我使用.NET Framework 4。 在我的表单中,我有41个文本框。 我已经尝试使用此代码: private void ClearTextBoxes() { Action<Control.ControlCollection> func = null; func = (controls) => { foreach (Control control in controls) if (control is TextBox) (control as TextBox).Clear(); else func(control.Controls); }; func(Controls); } 这段代码: private void ClearTextBoxes(Control.ControlCollection cc) { foreach (Control ctrl in cc) { TextBox tb = ctrl as TextBox; if (tb != null) tb.Text = String.Empty; else ClearTextBoxes(ctrl.Controls); } } 这仍然对我不起作用。 当我尝试使用此代码清除TextBoxName.Text = String.Empty; 文本框成功清除,但是一个文本框仍然有40个文本框。 我该如何解决? 编辑 我输入:
  • Why doesn't the Controls collection provide all of the IEnumerable methods?
    I'm not for sure how the ControlCollection of ASP.Net works, so maybe someone can shed some light on this for me. I recently discovered the magic that is extension methods and Linq. Well, I was very sad to find that this isn't valid syntax var c=Controls.Where(x => x.ID=="Some ID").SingleOrDefault(); However from what I can tell, Controls does implement the IEnumerable interface which provides such methods, so what gives? Why doesn't that just work? I have found a decent work around for this issue at least: var list = (IEnumerable<Control>)Controls; var this_item = list.Where(x => x.ID ==
  • 明确的控制措施不会将其处置-有什么风险?(Clear controls does not dispose them - what is the risk?)
    问题 关于.NET组件容器中的Clear ()项不处理(通过调用Dispose( true ))的事实,有多个线程(a,b,c等)。 恕我直言,最常见的是,应用程序中不再使用Clear-ed组件,因此需要在从父容器中清除它们之后将其明确地处置。 也许一个好主意是,集合的Clear方法有一个bool参数来dispose ,当为true时,它还在从列表中删除之前也处理了集合元素吗? 回答1 要求这样的修改是没有意义的,Windows窗体团队已经解散了很久了。 它处于维护模式,仅考虑安全问题和操作系统不兼容。 否则,创建您自己的方法来执行此操作就足够简单了: public static class ExtensionMethods { public static void Clear(this Control.ControlCollection controls, bool dispose) { for (int ix = controls.Count - 1; ix >= 0; --ix) { if (dispose) controls[ix].Dispose(); else controls.RemoveAt(ix); } } } 现在您可以编写: panel1.Controls.Clear(true); 回答2 回答“什么是风险”问题,尽管可能要花一些时间,但该风险(或某种风险
  • 回发后将焦点设置在文本框上(Set focus on textbox after postback)
    问题 我有一个包含 3 个文本框的搜索页面,用户可以使用它来过滤搜索。 我已将焦点放在包含文本的 TextBox 上。 如果不止一个包含文本,只需关注最后一个 TextBox。 private void SetFocusOnTextBox(ControlCollection ctrlCollection) { foreach (Control ctrl in ctrlCollection) { if (ctrl.GetType() == typeof(TextBox)) { if (((TextBox)ctrl).Text != string.Empty) { SetFocus(ctrl); } } } } 在代码运行和用户搜索后,焦点将转到 TextBox 的开头,而不是假定的结尾。 如何将插入标记在该文本框的末尾? 回答1 我认为答案在这里:使用 JavaScript 将光标置于文本输入元素中的文本末尾。 取自链接的解决方案:您必须将onfocus="this.value = this.value"到标记文件中的三个控件中。 这在 ASP.NET 中并不容易,但一种解决方案是在代码隐藏文件中添加属性。 protected void Page_Init(object sender, EventArgs e) { // MoveCursorToEndOnFocus is
  • Enumerating Collections that are not inherently IEnumerable?
    When you want to recursively enumerate a hierarchical object, selecting some elements based on some criteria, there are numerous examples of techniques like "flattening" and then filtering using Linq : like those found here : link text But, when you are enumerating something like the Controls collection of a Form, or the Nodes collection of a TreeView, I have been unable to use these types of techniques because they seem to require an argument (to the extension method) which is an IEnumerable collection : passing in SomeForm.Controls does not compile. The most useful thing I found was this
  • Clear multiple text boxes with a button in C#
    I use .NET framework 4. In my form, I have 41 text boxes. I have tried with this code: private void ClearTextBoxes() { Action<Control.ControlCollection> func = null; func = (controls) => { foreach (Control control in controls) if (control is TextBox) (control as TextBox).Clear(); else func(control.Controls); }; func(Controls); } And this code: private void ClearTextBoxes(Control.ControlCollection cc) { foreach (Control ctrl in cc) { TextBox tb = ctrl as TextBox; if (tb != null) tb.Text = String.Empty; else ClearTextBoxes(ctrl.Controls); } } This still does not work for me. When I tried to
  • 通过Tag属性找到WinForm控件的能力(Ability to find WinForm control via the Tag property)
    问题 我正在使用C#处理现有的WinForm项目。 原始代码使用Tag来传达一堆文本框的硬件寻址信息,这些文本框表示已连接的微控制器系统中的某些硬件寄存器。 我知道如何通过使用Control.ControlCollection.Find方法搜索其名称来找到未知控件,但是对于我是否可以通过Tag查找控件(在此实例中只是一个字符串),尚不清楚。 回答1 跟进我的评论: private void FindTag(Control.ControlCollection controls) { foreach (Control c in controls) { if (c.Tag != null) //logic if (c.HasChildren) FindTag(c.Controls); //Recursively check all children controls as well; ie groupboxes or tabpages } } 然后,您可以在if语句中获取控件名称,然后从那里开始执行任何操作。 只需在此解决方案中添加“编辑”即可,因为几年后它仍然很少得到Upvote的支持。 您还可以修改此解决方案以检查c的控件类型,并执行不同种类的逻辑。 因此,如果您想遍历所有控件并以一种方式处理Textbox并以另一种方式处理RadioButon也可以这样做。
  • 枚举不是固有的IEnumerable的集合?(Enumerating Collections that are not inherently IEnumerable?)
    问题 当您想递归枚举层次对象,根据某些条件选择一些元素时,有许多技术示例,例如“展平”然后使用Linq进行过滤:就像在这里找到的那​​样: 连结文字 但是,当您枚举诸如Forms的Controls集合或TreeView的Nodes集合之类的东西时,我一直无法使用这些类型的技术,因为它们似乎需要一个(IEnumerable的)自变量(对扩展方法)集合:传入SomeForm.Controls不会编译。 我发现最有用的是: 连结文字 确实为您提供了Control.ControlCollection的扩展方法,并提供了IEnumerable结果,您可以将其与Linq一起使用。 我已经修改了上面的示例,可以毫无问题地解析TreeView的节点。 public static IEnumerable<TreeNode> GetNodesRecursively(this TreeNodeCollection nodeCollection) { foreach (TreeNode theNode in nodeCollection) { yield return theNode; if (theNode.Nodes.Count > 0) { foreach (TreeNode subNode in theNode.Nodes.GetNodesRecursively()) { yield return
  • VB.NET-遍历容器对象中的控件(VB.NET - Iterating through controls in a container object)
    问题 我有一个带有“清除”按钮的表格。 当用户单击“清除”时,我想清除表单上所有可见元素的值。 对于日期控件,我想将它们重置为当前日期。 我所有的控件都包含在面板中。 现在,我正在使用以下代码进行此操作。 是否有比手动检查每种控件类型更简单的方法? 这种方法似乎太笨拙了。 更糟糕的是,为了递归地清除子容器内的控件(即面板内的一个组框),我必须使用“ GroupBox”版本重载来重复整个怪物。 编辑:由于您的建议,下面的代码大大简化了。 Private Sub btnClear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClear.Click 'User clicks Clear, so clear all the controls within this panel ClearAllControls(panMid, True) 'True indicates that yes, i want to recurse through sub-containers End Sub ClearAllControls(ByRef container As Panel, Optional Recurse As Boolean = True) 'Clear all of the
  • VB.NET - Iterating through controls in a container object
    I have a form with a "Clear" button. When the user clicks "Clear", I want to clear the value of all the visible elements on the form. In the case of date controls, I want to reset them to the current date. All of my controls are contained on a Panel. Right now, I'm doing this with the below code. Is there an easier way than manually checking for each control type? This method seems excessively unwieldy. To make matters worse, in order to recursively clear controls inside sub-containers (i.e., a group box within the panel) I have to repeat the whole monster with an overloaded "GroupBox" version
  • 如何在Windows窗体中获取窗体的所有控件?(How do I get all controls of a form in Windows Forms?)
    问题 我有一个名为A的Form 。 A包含许多不同的控制,包括主要的GroupBox 。 该GroupBox包含许多表和其他GroupBox 。 我想找到一个控件,例如在A窗体中具有选项卡索引9,但是我不知道哪个GroupBox包含此控件。 我怎样才能做到这一点? 回答1 随着递归... public static IEnumerable<T> Descendants<T>( this Control control ) where T : class { foreach (Control child in control.Controls) { T childOfT = child as T; if (childOfT != null) { yield return (T)childOfT; } if (child.HasChildren) { foreach (T descendant in Descendants<T>(child)) { yield return descendant; } } } } 您可以使用上述功能,例如: var checkBox = (from c in myForm.Descendants<CheckBox>() where c.TabIndex == 9 select c).FirstOrDefault()
  • 遍历窗体的所有控件,甚至是GroupBoxes中的控件(Loop through all controls of a Form, even those in GroupBoxes)
    问题 我想向我的Form上的所有TextBoxes添加一个事件: foreach (Control C in this.Controls) { if (C.GetType() == typeof(System.Windows.Forms.TextBox)) { C.TextChanged += new EventHandler(C_TextChanged); } } 问题在于它们存储在几个GroupBox中,而我的循环看不到它们。 我可以单独遍历每个GroupBox控件,但是是否可以在一个循环中以一种简单的方式完成所有这些操作? 回答1 窗体和容器控件的Controls集合仅包含直接子级。 为了获得所有控件,您需要遍历控件树并递归应用此操作 private void AddTextChangedHandler(Control parent) { foreach (Control c in parent.Controls) { if (c.GetType() == typeof(TextBox)) { c.TextChanged += new EventHandler(C_TextChanged); } else { AddTextChangedHandler(c); } } } 注意:该表单也(间接)派生​​自Control ,并且所有控件都具有Controls集合。 因此
  • async Task vs async void
    This might be a very stupid question, but I have the following lines of coding that convert RAW images to BitmapImages: public async void CreateImageThumbnails(string imagePath, int imgId) { await Task.Run(() => controlCollection.Where(x => x.ImageId == imgId).FirstOrDefault().ImageSource = ThumbnailCreator.CreateThumbnail(imagePath)); } which calls this method CreateThumbnail() public static BitmapImage CreateThumbnail(string imagePath) { var bitmap = new BitmapImage(); using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read)) { bitmap.BeginInit(); bitmap.DecodePixelWidth
  • 异步任务与异步无效(async Task vs async void)
    问题 这可能是一个非常愚蠢的问题,但是我有以下几行代码将RAW图像转换为BitmapImages: public async void CreateImageThumbnails(string imagePath, int imgId) { await Task.Run(() => controlCollection.Where(x => x.ImageId == imgId).FirstOrDefault().ImageSource = ThumbnailCreator.CreateThumbnail(imagePath)); } 调用此方法CreateThumbnail() public static BitmapImage CreateThumbnail(string imagePath) { var bitmap = new BitmapImage(); using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read)) { bitmap.BeginInit(); bitmap.DecodePixelWidth = 283; bitmap.CacheOption = BitmapCacheOption.OnLoad; bitmap.StreamSource = stream
  • 获取特定类型的所有控件(Get all controls of a specific type)
    问题 我有多个图片框,并且需要在运行时将随机图片加载到其中。 因此,我认为最好有一个所有图片框的集合,然后使用一个简单的循环将图像分配给它们。 但是我该怎么办呢? 也许对于这个问题还有其他更好的解决方案吗? 回答1 使用一些LINQ: foreach(var pb in this.Controls.OfType<PictureBox>()) { //do stuff } 但是,这只会处理主容器中的PictureBoxes。 回答2 您可以使用以下方法: public static IEnumerable<T> GetControlsOfType<T>(Control root) where T : Control { var t = root as T; if (t != null) yield return t; var container = root as ContainerControl; if (container != null) foreach (Control c in container.Controls) foreach (var i in GetControlsOfType<T>(c)) yield return i; } 然后,您可以执行以下操作: foreach (var pictureBox in GetControlsOfType<PictureBox
  • Properly disposing of, and removing references to UserControls, to avoid memory leak
    I'm developing a Windows Forms application (.NET 4.0) in c# using Visual c# express 2010. I'm having trouble freeing up memory allocated to UserControls I'm no-longer using. The problem: I have a FlowLayoutPanel, where custom UserControls are displayed. The FlowLayoutPanel displays search results and so on, so the collection of UserControls that are displayed must be repeatedly updated. Before every new set of these UserControls are created and displayed, Dispose() is called on all the Controls currently contained in my FlowLayoutPanel's ControlCollection (Controls property), then Clear() is
  • 从代码隐藏中删除 asp.net 控件(Remove an asp.net control from code-behind)
    问题 当某个条件得到验证时,我需要从我的页面中删除一个控件(一个文本框)。 是否可以从代码隐藏中执行,或者我需要使用 JavaScript。 注意我需要删除控件,而不是隐藏... 回答1 在父ControlCollection上使用 Controls.Remove 或 Controls.RemoveAt。 例如,如果要从页面顶部删除所有文本框: var allTextBoxes = Page.Controls.OfType<TextBox>().ToList(); foreach(TextBox txt in allTextBoxes) Page.Controls.Remove(txt); (请注意,您需要using System.Linq添加Enumerable.OfType ) 或者如果要删除具有给定 ID 的 TextBox: TextBox textBox1 = (TextBox)Page.FindControl("TextBox1"); // note that this doesn't work when you use MasterPages if(textBox1 != null) Page.Controls.Remove(textBox1); 如果你只是想隐藏它(并从客户端完全删除它),你也可以让它不可见: textBox1.Visible = false