2010-02-09 74 views
20

我來自功能編程背景,所以如果我不理解C#中的閉包,請原諒我。C#事件處理程序委託中的閉包?

我有以下代碼,以動態生成得到匿名事件處理程序的按鈕:

for (int i = 0; i < 7; i++) 
{ 
    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + i); 
    }; 

    this.Controls.Add(newButton); 
} 

我預計文本"I am button number " + i同在的for循環迭代的i價值被關閉。但是,當我真正運行該程序時,每個按鈕都表示I am button number 7。我錯過了什麼?我正在使用VS2005。

編輯:所以我想我的下一個問題是,我如何捕獲價值?

+4

您不捕獲該值。你永遠不會捕獲值,只有變量。有關此問題的更多信息,請參閱http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx和http://blogs.msdn .com/ericlippert/archive/2009/11/16/closing-the-loop-variable-part-two.aspx – 2010-02-09 06:37:42

回答

26

要獲得這種行爲,你需要的變量本地複製,不使用迭代器:

for (int i = 0; i < 7; i++) 
{ 
    var inneri = i; 
    Button newButton = new Button(); 
    newButton.Text = "Click me!"; 
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + inneri); 
    }; 
    this.Controls.Add(newButton); 
} 

推理在更多的細節in this question討論。

4

閉包捕獲的變量不是值。這意味着在執行代理時,即在循環結束後的某個時間,i的值爲6.

要捕獲值,請將其分配給循環體中聲明的變量。在循環的每次迭代中,將爲其中聲明的每個變量創建一個新實例。

Jon Skeet的articles on closures有一個更深的解釋和更多的例子。

for (int i = 0; i < 7; i++) 
{ 
    var copy = i; 

    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + copy); 
    }; 

    this.Controls.Add(newButton); 
} 
+0

-1:一個更翔實的答案,深入解釋了一個例子應該發生的事情被評爲更高。 – IAbstract 2010-02-13 05:53:15

1

到時候你點擊任何鏈接,他們已經全部從1生成到7,所以他們都將表達我的最終狀態是7

4

您已經創建七位代表,但每個代表舉行提及同一個實例 i

當點擊按鈕時,MessageBox.Show功能僅被稱爲。在點擊按鈕時,循環已經完成。所以,此時i將等於七。

試試這個:

for (int i = 0; i < 7; i++) 
{ 

    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    int iCopy = i; // There will be a new instance of this created each iteration 
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + iCopy); 
    }; 

    this.Controls.Add(newButton); 
} 
23

尼克是正確的,但我想解釋這個問題的文字好一點正是爲什麼

問題不在於關閉;這是for-loop。循環只爲整個循環創建一個變量「i」。它不會爲每次迭代創建一個新的變量「i」。 注:這據說改變了C#5

這意味着當你的匿名委託捕獲或關閉了「我」變量它關閉了由所有按鈕共享一個變量。當你真的點擊其中任何一個按鈕時,循環已經完成將該變量遞增到7。

有一件事我可以從尼克的代碼有什麼不同的是使用一個字符串內的變量和按鈕按下時開始構建所有這些字符串前面,而不是像這樣:

for (int i = 0; i < 7; i++) 
{ 
    var message = string.Format("I am button number {0}.", i); 

    Button newButton = new Button(); 
    newButton.Text = "Click me!"; 
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show(message); 
    }; 
    this.Controls.Add(newButton); 
} 

這只是交易一點點的內存(稍等一會兒,稍等一會cpu時間)(取決於較大的字符串變量而不是整數)......這取決於您的應用程序的重要性。

另一種選擇是不是手工編碼的循環都:

this.Controls.AddRange(Enumerable.Range(0,7).Select(i => 
{ 
    var b = new Button() {Text = "Click me!", Top = i * 20}; 
    b.Click += (s,e) => MessageBox.Show(string.Format("I am button number {0}.", i)); 
    return b; 
}).ToArray()); 

我喜歡這最後一個選項不是那麼多,因爲它消除了環路,而是因爲它開始你建立的思維從這個控制數據源。

+0

+1進一步改進! – 2010-02-09 04:12:51

+0

這不是一個錯誤,但他們正在改變它。就像Silverlight是一個可行的框架(但可能沒有獲得任何新功能並減少/退役支持)。 – micahhoover 2014-04-17 14:03:20