foreach和linq查询 - 需要帮助试图理解请

Joh*_*ohn 7 c# linq foreach

我有一个linq查询,其结果我在foreach循环中迭代.

第一个是从表布局面板中获取控件集合的查询,然后迭代集合并从tableLayoutPanel中删除控件:

var AllItems = (from Item in this.tableLayoutPanel1.Controls.OfType<ItemControl>()
                select Item);

foreach (ItemControl item in AllItems)
{
    Trace.WriteLine("Removing " + item.ToString());
    this.tableLayoutPanel1.Controls.Remove(item);
    item.Dispose();
}
Run Code Online (Sandbox Code Playgroud)

上面没有按照我的预期进行(即抛出一个错误),它只删除了一半的控件(ODD编号为1),看起来每次迭代时AllItems都会减少它的自身,虽然底层集合被修改但没有错误是抛出.

如果我用一串字符串做同样的话:

        string[] strs = { "d", "c", "A", "b" };
        List<string> stringList = strs.ToList();

        var allitems = from letter in stringList
                       select letter;

        foreach (string let in allitems)
        {
            stringList.Remove(let);

        }
Run Code Online (Sandbox Code Playgroud)

这次Visual Studio抛出一个错误(正如预期的那样)抱怨底层集合已经改变.

为什么第一个例子不会爆炸呢?

有一些关于Iterators/IEnumerable的东西,我在这里不理解,我想知道是否有人可以帮助我了解linq和foreach引擎盖下发生了什么.

(我知道我可以通过AllItems.ToList()解决这两个问题;在迭代之前,但是想要理解为什么第二个例子抛出错误而第一个没有

Ree*_*sey 10

为什么第一个例子不会爆炸呢?

其中的IEnumerator实现tableLayoutPanel1.Controls没有执行适当的检查以查看集合是否已被修改.这并不意味着它是一个安全的操作(因为结果不合适),而是该类没有以引发异常的方式实现(因为它可能应该).

枚举List<T>和删除项目时引发的异常实际上是由List<T>.Enumerator类型引发的,并不是"通用"语言功能.

请注意,这是该类型的一个很好的功能List<T>.Enumerator,因为IEnumerator<T>文档明确指出:

只要集合保持不变,枚举器仍然有效.如果对集合进行了更改(例如添加,修改或删除元素),则枚举数将无法恢复,并且其行为未定义.

虽然没有合同要求抛出异常,但当您进入"未定义行为"领域时,实现这样做是有益的.

在这种情况下,TableLayoutControlCollection类枚举器实现不执行额外检查,因此您将看到当您使用"行为未定义"的功能时会发生什么.(在这种情况下,它会删除所有其他控件.)

  • @TimSchmelter嗯,`OfType <T>`只是通过直接使用源枚举器进行枚举 - 虽然它没有进行检查,但是如果你枚举了对象并将检查放在循环中,就会发生同样的事情.底层枚举器未检查是否修改了控件集合. (2认同)