Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/301.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 事件处理程序委托中的闭包?_C#_Delegates_Anonymous Methods_Closures - Fatal编程技术网

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

C# 事件处理程序委托中的闭包?,c#,delegates,anonymous-methods,closures,C#,Delegates,Anonymous Methods,Closures,我目前来自函数编程背景,如果我不理解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

我目前来自函数编程背景,如果我不理解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(int i=0;i<7;i++)
{
按钮newButton=新按钮();
Text=“单击我!”;
newButton.Click+=委托(对象发送方,事件参数e)
{
MessageBox.Show(“我是按钮编号”+I);
};
this.Controls.Add(newButton);
}
我希望文本
“我是按钮编号”+I
在for循环的迭代中以
I
的值关闭。然而,当我实际运行该程序时,每个按钮都会显示
我是按钮编号7
。我错过了什么?我正在使用VS2005


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

闭包捕获变量而不是值。这意味着在执行委托时,即循环结束后的某个时间,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);
}
for(int i=0;i<7;i++)
{
var copy=i;
按钮newButton=新按钮();
Text=“单击我!”;
newButton.Click+=委托(对象发送方,事件参数e)
{
MessageBox.Show(“我是按钮号”+副本);
};
this.Controls.Add(newButton);
}

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

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(int i=0;i<7;i++)
{
var inneri=i;
按钮newButton=新按钮();
Text=“单击我!”;
newButton.Click+=委托(对象发送方,事件参数e)
{
MessageBox.Show(“我是按钮编号”+inneri);
};
this.Controls.Add(newButton);
}

推理将被更详细地讨论。

当您单击任何按钮时,它们都是从1到7生成的,因此它们都将表示i的最终状态,即7。

您已经创建了七个委托,但每个委托都持有对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); 
}
for(int i=0;i<7;i++)
{ 
按钮newButton=新按钮();
Text=“单击我!”;
int-iCopy=i;//每次迭代都会创建一个新的实例
newButton.Click+=委托(对象发送方,事件参数e)
{ 
MessageBox.Show(“我是按钮编号”+iCopy);
}; 
this.Controls.Add(newButton);
}

尼克说得对,但我想在这个问题的正文中更好地解释一下原因

问题不在于结束;这是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);
}

我喜欢最后一个选项,不是因为它删除了循环,而是因为它让您开始思考如何从数据源构建此控件。

您无法捕获值。您从不捕获值,只捕获变量。有关此问题的更多信息,请参见和-1:一个更具信息性的答案,该答案深入地解释了一个示例发生的情况,应该给予更高的评级。这不是一个bug,但他们正在改变它。正如Silverlight是一个可行的框架(但可能永远不会获得任何新功能,并且支持减少/失效)。
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());