C#事件处理程序委托中的闭包?

我现在来自function编程背景,如果我不理解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的值关闭。 但是,当我实际运行程序时,每个Button都说I am button number 7 。 我错过了什么? 我正在使用VS2005。

编辑:所以我想我的下一个问题是,如何捕捉价值?

要获得此行为,您需要在本地复制变量,而不是使用迭代器:

 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); } 

在这个问题中更详细地讨论了推理。

尼克说得对,但我想在这个问题的文本中解释一下为什么

问题不在于关闭; 这是for-loop。 循环只为整个循环创建一个变量“i”。 它不会为每次迭代创建一个新变量“i”。 注意:据报道,C#5已经改变了。

这意味着当您的匿名委托捕获或关闭该“i”变量时,它将关闭所有按钮共享的一个变量。 当您实际点击任何这些按钮时,循环已经完成将该变量增加到7。

我可能与Nick的代码做的不同之处是使用字符串作为内部变量并在前面而不是按下按钮的时候构建所有这些字符串,如下所示:

 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()); 

我喜欢这个最后一个选项并不是因为它删除了循环但是因为它开始考虑从数据源构建这个控件。

闭包捕获变量而不是值。 这意味着在委托执行时,即循环结束后的某个时间,i的值为6。

要捕获值,请将其分配给循环体中声明的变量。 在循环的每次迭代中,将为其中声明的每个变量创建一个新实例。

Jon Skeet 关于闭包的文章有更深入的解释和更多的例子。

 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); } 

您已创建了七个委托,但每个委托都拥有对同一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); } 

当你点击任何按钮时,它们都是从1到7生成的,所以它们都将表示i的最终状态为7。