7

我有一个 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();
}

上面没有按我的预期做(即抛出一个错误),它只删除了一半的控件(奇数编号的)似乎在每次迭代中 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(); 在迭代之前解决这两个问题,但想了解为什么第二个示例会引发错误而第一个不会)

4

3 回答 3

10

为什么第一个例子也没有爆炸?

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

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

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

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

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

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

于 2012-07-19T17:34:28.717 回答
2

所以在我的第一种情况下,linq 表达式是否在 foreach 循环的每次迭代中都被重新评估?还是 linq 从 tablelayoutpanel.controls 集合中获取 IEnumerator?

把它想象成是这样发生的,这两种情况都发生了:

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

上面的伪代码说明,当您尝试删除一个项目时,您基本上处于stringList集合枚举的中间。Reed 的意思是,在第二种情况下,枚举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我们将不再触及stringListforeach 循环中的集合。

    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); 
    } 
于 2012-07-19T20:48:40.983 回答
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);
        }
于 2012-07-19T23:59:41.523 回答