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

erj*_*ang 21 c# delegates closures anonymous-methods

我现在来自功能编程背景,如果我不理解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);
}
Run Code Online (Sandbox Code Playgroud)

我希望"I am button number " + iifor循环的迭代值关闭文本.但是,当我实际运行程序时,每个Button都会说I am button number 7.我错过了什么?我正在使用VS2005.

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

Nic*_*ver 27

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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


Joe*_*orn 23

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

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

这意味着当您的匿名委托捕获或关闭该"i"变量时,它将关闭一个由所有按钮共享的变量.当您实际点击任何这些按钮时,循环已经完成将该变量增加到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);
}
Run Code Online (Sandbox Code Playgroud)

这只是交换一点内存(保留更大的字符串变量而不是整数)稍后的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());
Run Code Online (Sandbox Code Playgroud)

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


And*_*erd 5

您已经创建了七个委托,但每个委托都持有对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); 
}
Run Code Online (Sandbox Code Playgroud)