我现在来自功能编程背景,如果我不理解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);
}
我希望在for循环的迭代中以"I am button number " + i
的值关闭文本i
。但是,当我实际运行程序时,每个Button都显示I am button number 7
。我错过了什么?我正在使用VS2005。
编辑:所以我想我的下一个问题是,如何捕捉价值?
答案 0 :(得分: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。
答案 1 :(得分:23)
Nick说得对,但我想在这个问题的文本中更好地解释为什么。
问题不在于关闭;这是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());
我最不喜欢这个选项,因为它删除了循环但是因为它开始考虑从数据源构建这个控件。
答案 2 :(得分: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);
}
答案 3 :(得分: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);
}
答案 4 :(得分:1)
当你点击任何按钮时,它们都是从1到7生成的,所以它们都将表示i的最终状态为7。