2010-02-09 109 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

相关问题