2012-01-13 33 views
5

我有以下方法:爲什麼c#不保留匿名委託調用的上下文?

static Random rr = new Random(); 
static void DoAction(Action a) 
{ 
    ThreadPool.QueueUserWorkItem(par => 
    { 
     Thread.Sleep(rr.Next(200)); 
     a.Invoke(); 
    }); 
} 

現在我把這種在一個for循環是這樣的:

for (int i = 0; i < 10; i++) 
{ 
    var x = i; 

    DoAction(() => 
    { 
     Console.WriteLine(i); // scenario 1 
     //Console.WriteLine(x); // scenario 2 
    }); 
} 

在場景1的輸出是:10 10 10 10 ... 10
在場景2的輸出是: 2 6 5 8 4 ... 0(0到9的隨機排列)

你如何解釋這個? c#不應該爲匿名委託調用保留變量(這裏是i)?

+2

但保留( '捕獲')的*變量*'i'是*正是*發生了什麼事! *儘管你希望它發生*,但是在代表成立時保留* i的**值**。 – AakashM 2012-01-13 16:38:37

+2

順便提及,ReSharper的將發出警告關於[訪問改性閉合](http://confluence.jetbrains.net/display/ReSharper/Access+to+modified+closure)時與此代碼呈現;你可能會發現有幫助的解釋。 – AakashM 2012-01-13 16:40:38

+0

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx – 2012-01-13 17:03:31

回答

10

這裏的問題是有一個i變量和x的十個實例/副本。每個lambda獲得對單個變量i的引用和x的一個實例。每個x僅寫入一次,因此每個lambda都會看到寫入其所引用值的一個值。

變量i寫入,直到達到10無的lambda表達式的運行,直到循環完成,使他們都看到的i最終值是10

我覺得這個例子是,如果你更清楚一點它改寫如下

int i = 0; // Single i for every iteration of the loop 
while (i < 10) { 
    int x = i; // New x for every iteration of the loop 
    DoAction(() => { 
    Console.WriteLine(i); 
    Console.WriteLine(x); 
    }); 
    i++; 
}; 
+1

更尖銳地指出,每'x'不只是寫只有一次,但是因爲它每次被初始化,所以是一個不同的'x'。循環的生命週期中有10個'x',只有一個'i'。 – 2012-01-13 17:05:27

1

我認爲你會得到與Java或任何面嚮對象語言相同的結果(不知道,但這裏似乎是合乎邏輯)。

i的範圍適用於整個循環,x的範圍適用於每次發生。

Resharper幫助您找到這類問題的首要位置。

1

DoAction產生線程,並立即返回。由線程從它的隨機睡眠喚醒時間,循環將完成,並i值將擁有先進一路10 x的價值,而另一方面,被捕獲並呼叫之前凍結,因此您將以隨機順序從09獲取所有值,具體取決於每個線程根據您的隨機數生成器進入睡眠的時間。