2010-03-10 49 views
13

我剛碰到最出人意料的行爲。我確信這樣做有一個很好的理由。有人可以幫忙解釋一下嗎?C#中的lambda如何在foreach中與枚舉器綁定?

考慮以下代碼:

var nums = new int[] { 1, 2, 3, 4 }; 
var actions = new List<Func<int>>(); 

foreach (var num in nums) 
{ 
    actions.Add(() => num); 
} 

foreach (var num in nums) 
{ 
    var x = num; 
    actions.Add(() => x); 
} 

foreach (var action in actions) 
{ 
    Debug.Write(action() + " "); 
} 

輸出是對我來說有點出人意料:

4 4 4 4 1 2 3 4 

顯然有什麼東西與拉姆達是如何引用枚舉回事。在foreach的第一個版本中,'num'實際上是綁定到'Current',而不是它返回的結果?

+2

你似乎已經自己想通了。 :) – ChaosPandion 2010-03-10 00:37:53

+0

我推斷了行爲,但不是機制。我希望能得到像Greg D's這樣的解釋膽量的答案。 – scobi 2010-03-10 00:51:46

回答

7

這是衆所周知的,並建立了有關lambda的行爲,雖然經常令人驚訝的第一次遇到它的人。根本的問題是,你的心智模型是什麼λ是不完全正確的。

lambda是一個函數,它在調用之前不會運行。你的閉包綁定了對該lambda實例的引用,而不是值。當你在你的最終foreach循環中執行你的動作時,這是你第一次真正關注封閉引用來看看它是什麼。

在第一種情況下,您引用了num,並且此時num的值是4,所以當然所有的輸出都是4.在第二種情況下,每個lambda綁定到不同的值每次都是循環本地的,而且這個值沒有改變(因爲lambda引用,它沒有被GC'd修改過)。因此,你得到了你期望的答案。

本地臨時值上的閉包實際上是從lambda內的時間點捕獲特定值的標準方法。

Adam的link to Eric Lippert's blog提供了一個更深入的(和技術上準確的)描述正在發生的事情。

3

有關此問題,請參閱Eric Lippert's blog post;它與代碼中迭代器變量作用域的相關性如何,以及它如何應用於lambda封閉和懸掛函數。

+0

是我還是鏈接已經死了?我希望閱讀Eric Lippert關於這個話題的評論。 – 2010-03-10 00:44:50

+0

我誤用了網址。現在已修好。 – 2010-03-10 00:47:41

+0

喲....服務器錯誤404在這裏 – hallie 2010-03-10 00:47:54

1

這是由於以下兩個方面:
1)代表保存外部變量
2)第一週期的foreach的上下文(範圍)將在聲明僅一個「NUM」可變編譯。
3)延遲評估

在第一個週期中添加的每個委託將保存保存到該範圍的同一個num變量。由於懶惰的評估,你會在第一個週期完成後運行代表,因此所有數據都可以保存到代表的範圍等於4.

2

由於foreach構造只是語法糖,所以最好把它想象成它的真正的形式。

int num; 
while (nums.MoveNext()) 
{ 
    num = nums.Current; 
    actions.Add(() => num); 
} 

拉姆達將捕獲num變量,所以當你執行將使用num最新值的拉姆達。