C# 事件处理程序委托中的闭包? [复制]
Posted
技术标签:
【中文标题】C# 事件处理程序委托中的闭包? [复制]【英文标题】:Closures in C# event handler delegates? [duplicate] 【发布时间】:2010-02-09 03:13:12 【问题描述】:目前我来自函数式编程背景,如果我不了解 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。
编辑:所以我想我的下一个问题是,我如何捕获价值?
【问题讨论】:
你没有捕捉到价值。您永远不会捕获值,只会捕获变量。有关此问题的更多信息,请参阅 blogs.msdn.com/ericlippert/archive/2009/11/12/… 和 blogs.msdn.com/ericlippert/archive/2009/11/16/… 【参考方案1】:要获得这种行为,您需要在本地复制变量,而不是使用迭代器:
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。
【讨论】:
【参考方案2】:尼克说得对,但我想在这个问题的正文中更好地解释一下为什么。
问题不在于关闭;这是for循环。该循环只为整个循环创建一个变量“i”。它不会为每次迭代创建一个新变量“i”。 注意:据报道,这在 C# 5 中有所改变。
这意味着当您的匿名委托捕获或关闭该“i”变量时,它正在关闭一个由所有按钮共享的变量。当您真正点击这些按钮时,循环已经完成将该变量增加到 7。
我可能做的与 Nick 的代码不同的一件事是使用一个字符串作为内部变量,并在前面而不是在按下按钮时构建所有这些字符串,如下所示:
for (int i = 0; i < 7; i++)
var message = $"I am button number 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($"I am button number i.");
return b;
).ToArray());
我不太喜欢最后一个选项,因为它消除了循环,而是因为它让您开始思考如何从数据源构建此控件。
【讨论】:
这不是错误,但他们正在更改它。就像 Silverlight 是一个可行的框架一样(但可能永远不会获得任何新功能并且支持减少/停用)。【参考方案3】:您创建了七个委托,但每个委托都持有对同一 i 实例的引用。
MessageBox.Show
函数仅在单击按钮时调用。单击按钮时,循环已完成。因此,此时 i
将等于 7。
试试这个:
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】:闭包捕获的是变量而不是值。这意味着到执行委托时,即循环结束后的某个时间,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);
【讨论】:
-1:一个更丰富的答案,深入解释了一个例子发生的事情应该被评为更高。【参考方案5】:到你点击任意按钮的时候,它们都是从1到7生成的,所以它们都会表示i的最终状态,即7。
【讨论】:
以上是关于C# 事件处理程序委托中的闭包? [复制]的主要内容,如果未能解决你的问题,请参考以下文章