2012-07-19 72 views
7

我有一個linq查詢,我在foreach循環中迭代的結果。foreach和linq查詢 - 需要幫助試圖瞭解請

首先是其抓住的控件的集合從表佈局面板的查詢時,我然後遍歷集合並因此從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編號的) 看來,在每次迭代中AllItems都會減少它的自身,並且儘管正在修改底層集合,但不會引發錯誤。

如果我有一個字符串數組simmilar:

 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拋出抱怨底層集合已變化的情況下(如預期)。

爲什麼第一個例子不會爆炸呢?

有一些關於迭代器/ IEnumerable,我不理解這裏,我不知道是否有人可以幫助我瞭解linq和foreach在引擎蓋下發生了什麼。

(我知道我可以AllItems.ToList()治癒了這兩個問題,我以前迭代,但想明白爲什麼第二個例子中拋出一個錯誤,先不)

回答

10

爲什麼第一個例子不會爆炸嗎?

tableLayoutPanel1.ControlsIEnumerator實現沒有執行正確的檢查,看是否集合已經被修改。這並不意味着這是一個安全的操作(因爲結果不合適),而是該類沒有以引發異常的方式實現(因爲它可能應該)。

當您枚舉List<T>並刪除項目時引發的異常實際上由List<T>.Enumerator類型引發,並且不是「通用」語言功能。

注意,這是一個不錯的功能的List<T>.Enumerator,爲IEnumerator<T>文件明確規定:

枚舉器仍然只要集合保持不變,有效。如果對集合進行更改(如添加,修改或刪除元素),則枚舉器將無法恢復無效,並且其行爲未定義。

雖然沒有需要拋出異常的合約,但當您進入「未定義行爲」領域時,這對於執行操作是有利的。

在這種情況下,TableLayoutControlCollection類枚舉器實現未執行額外的檢查,因此您會看到在使用「行爲未定義」特性時會發生什麼情況。 (在這種情況下,它將刪除所有其他控件。)

+0

那是'Enumerable.OfType'通過使用延遲執行實現了這個問題不相關的事實的原因,因此序列總是在每一個枚舉變化? – 2012-07-19 17:43:44

+2

@TimSchmelter好了,'OfType '只是列舉了通過直接使用源枚舉 - 雖然它沒有做檢查,同樣的事情會,如果你列舉的對象發生,並把內循環檢查。底層枚舉器沒有檢查控件集合是否被修改。 – 2012-07-19 17:47:30

+0

@TimSchmelter:我懷疑所有的LINQ操作可能會從原來的職位代碼被刪除,它會表現得完全一樣的方式。正如裏德所說,這是普查員實施的結果。 OfType與它無關。 – StriplingWarrior 2012-07-19 17:51:54

2

所以在我的第一種情況是linq表達式重新評估每個 迭代的foreach循環?或者是linq從 tablelayoutpanel.controls集合獲取IEnumerator?

認爲它是如果它發生這樣的是正在發生的事情在這兩個您的情況:

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,我們將不再碰stringList集合中的foreach循環。

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

謝謝布拉德。我現在明白了。 @everyone誰回答:謝謝你,這真的清除在我的腦海這個問題了。你們好棒! – John 2012-07-19 20:56:24

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