2016-02-12 78 views
4

在C#中試用閉包時,我發現如果它們在循環中捕獲迭代器變量,它們會意外地工作。在for和foreach循環中閉包的行爲不同

var actions = new List<Action>(); 

foreach (int i in new[] { 1, 2 }) 
    actions.Add(() => Console.WriteLine(i)); 

for (int i = 3; i <= 4; i++) 
    actions.Add(() => Console.WriteLine(i)); 

foreach (var action in actions) 
    action(); 

以上代碼生成一個奇怪結果(我使用.NET 4.5編譯器):

1 
2 
5 
5 

爲什麼的i值捕獲不同2個幾乎相同的循環?

+0

你注意到這個微妙的區別是聰明的。是的,這裏有一個危險,有人可以重構一個foreach到一個for,並沒有意識到他們正在引入語義變化!我很有興趣知道,如果您對「for」案件有「真實世界」使用案例,特別是「for」奇怪行爲是期望行爲的案例。 –

回答

7

在C#5和超越,foreach環聲明瞭循環的每次迭代一個單獨i變量。所以每個閉包都會捕獲一個單獨的變量,並且您會看到預期的結果。

for循環,你只有一個i變量,它是由所有的封閉拍攝,並且修改循環的進展 - 所以,當你調用代表的時候,你看到的最終值單變量。

在C#2,3和4所示,foreach環路表現這種方式爲好,這是基本上從未所期望的行爲,所以它是固定在C#5

可以實現相同的效果在for循環,如果你的循環體的範圍內引入新的變量:

for (int i = 3; i <= 4; i++) 
{ 
    int copy = i; 
    actions.Add(() => Console.WriteLine(copy)); 
} 

欲瞭解更多詳情,請閱讀埃裏克利珀的博客文章,「關閉了被認爲是有害的循環變量」 - part 1part 2

0

在foreach情況下,它在本地變量中保存值,所以它在每個委託中都有它自己的值,而在for循環情況下則不是這樣,在for循環情況下,所有委託都指向相同的i,所有代表均使用i中更新的最後一個值。

這是在foreach循環的情況下的一個突破性變化,在舊版本中它們都曾以相同的方式工作。