2012-03-05 70 views
6

我無法理解如何遍歷Action列表。當我嘗試它時,我最終得到的值與之前的迭代相同。循環顯示一系列操作

下面的代碼(簡化的示例):

string[] strings = { "abc", "def", "ghi" }; 

var actions = new List<Action>(); 
foreach (string str in strings) 
    actions.Add(new Action(() => { Trace.WriteLine(str); })); 

foreach (var action in actions) 
    action(); 

輸出:

ghi 
ghi 
ghi 

爲什麼總是選擇最終元件中strings時所執行的操作?
我怎樣才能達到理想的輸出具體做法是:

abc 
def 
ghi 

回答

13

你的行動關閉,因此它訪問str本身,而不是一個的str副本:

foreach (string str in strings) 
{ 
    var copy = str; // this will do the job 
    actions.Add(new Action(() => { Trace.WriteLine(copy); })); 
} 
+0

嘎,你贏了。我知道如何解決這個問題,但我不記得原因。關閉!我需要關閉! +1 :) – Joshua 2012-03-05 15:37:25

+1

@Joshua這是不是很久以前,當我更深入地瞭解到:)這可能是進一步閱讀好http://stackoverflow.com/questions/9412672/lambda-expressions-with -m-cult -reading-in-c- – 2012-03-05 15:39:42

+0

有趣的是,我從來沒有意識到。謝謝。 – demoncodemonkey 2012-03-05 15:46:20

3

這是一個非常複雜的情況。簡短的回答是將其分配給關閉之前創建的局部變量的副本:

string copy = str; 
actions.Add(new Action(() => { Trace.WriteLine(copy); })); 

Check out this article on closures以獲取更多信息。

3

此行爲由Closures調整。

存在於您的lambda中的變量是參考而不是值副本。這意味着指向str所假定的最新值,在您的情況下爲「ghi」。這就是爲什麼每次調用只需進入相同的內存位置,並自然恢復相同的值。

如果你寫的代碼,如在提供的答案,你強制C#編譯器每次再生一個值,所以地址將被傳遞到labmda,所以每一個拉姆達都會有它的擁有變量。

順便說一句,如果我不誤,C#團隊承諾以修復這個C# 5.0自然行爲。因此,最好查看他們的博客關於這個主題的未來更新。

+2

+1很好的解釋。值得一提的是,Java以另一種方式進行操作。如果你處理'Runnable'(通常)來啓動一個線程,變量將被複制到新的上下文中。這也是Java爲什麼迫使你讓它們成爲'final'的原因。 – 2012-03-05 15:53:28