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

时间:2012-07-19 17:23:31

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

以上没有按照我的预期进行(即抛出错误),它只删除了一半的控件(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);

        }

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

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

有一些关于Iterators / IEnumerable的内容,我在这里并不理解,我想知道是否有人可以帮助我了解linq和foreach的内幕。

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

3 个答案:

答案 0 :(得分:10)

  

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

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

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

请注意,这是List<T>.Enumerator类型的精彩功能,因为IEnumerator<T>文档明确指出:

  

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

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

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

答案 1 :(得分:2)

  

所以在我的第一个案例中,linq表达式被重新评估   foreach循环的迭代?或者是linq从中获取IEnumerator   tablelayoutpanel.controls集合?

把它想象成就像这样发生在你的两种情况下发生的事情:

    foreach (string l in (from letter in stringList select letter)) 
    { 
        stringList.Remove(l); 
    } 

上面的伪代码说明当您尝试删除项目时,您基本上处于stringList集合的枚举中。里德所说的是,在第二种情况下,stringList的调查员确保在枚举的过程中没有人弄乱它。

他说,对于你的控件,控件枚举器很乐意继续进行,而不是检查是否有人在调整其数据。

为了完整起见,请比较以下两个例子:

每次触摸letters时,都会重新评估letters的此查询,其中包括我们在foreach循环中的时间:

    IEnumerable<string> letters = from letter in stringList select letter);

    // everytime we hit this we're going to hit the stringList collection
    foreach (string l in letters) 
    { 
        stringList.Remove(l); 
    } 

此查询使用ToList()立即填充letters,我们将不再触及foreach循环中的stringList集合。

    List<string> letters = (from letter in stringList select letter).ToList()

    // because we ran ToList(), we will no longer enumerate stringList
    foreach (string l in letters) 
    { 
        stringList.Remove(l); 
    } 

答案 2 :(得分:0)

试试这个:

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


  int totalCount = AllItems .Count;
   for (int i = 0; i < totalCount; i++)
       {
           AllItems .RemoveAt(0);
        }