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

发布于 2024-08-20 21:57:19 字数 547 浏览 6 评论 0原文

我目前有函数式编程背景,所以如果我不理解 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 将以 i 的值关闭for 循环的迭代。然而,当我实际运行该程序时,每个按钮都会显示我是按钮号 7。我缺少什么?我用的是VS2005。

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

I am coming from a functional-programming background at the moment, so forgive me if I do not understand closures in C#.

I have the following code to dynamically generate Buttons that get anonymous event handlers:

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 expected the text "I am button number " + i to be closed with the value of i at that iteration of the for loop. However, when I actually run the program, every Button says I am button number 7. What am I missing? I am using VS2005.

Edit: So I guess my next question is, how do I capture the value?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

近箐 2024-08-27 21:57:19

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

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

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

To get this behavior, you need to copy the variable locally, not use the iterator:

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

The reasoning is discussed in much greater detail in this question.

初与友歌 2024-08-27 21:57:19

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

问题不在于关闭,而在于关闭。这是 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());

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

Nick has it right, but I wanted to explain a little better in the text of this question exactly why.

The problem isn't the closure; it's the for-loop. The loop only creates one variable "i" for the entire loop. It does not create a new variable "i" for each iteration. Note: This has reportedly changed for C# 5.

This means when your anonymous delegate captures or closes over that "i" variable it's closing over one variable that is shared by all the buttons. By the time you actually get to click any of those buttons the loop has already finished incrementing that variable up to 7.

The one thing I might do differently from Nick's code is use a string for the inner variable and build all those strings up front rather than at button-press time, like so:

for (int i = 0; i < 7; i++)
{
    var message = 
quot;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);
}

That just trades a little bit of memory (holding on to larger string variables instead of integers) for a little bit of cpu time later on... it depends on your application what matters more.

Another option is to not manually code the loop at all:

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(
quot;I am button number {i}.");
    return b;
}).ToArray());

I like this last option not so much because it removes the loop but because it starts you thinking in terms of building this controls from a data source.

枯叶蝶 2024-08-27 21:57:19

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

You have created seven delegates, but each delegate holds a reference to the same instance of i.

The MessageBox.Show function is only called when the button is clicked. By the time the button has clicked, the loop has completed. So, at this point i will be equaling seven.

Try this:

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); 
}
铃予 2024-08-27 21:57:19

闭包捕获变量而不是值。这意味着在执行委托时,即在循环结束后的某个时间,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);
}

The closure captures the variable not the value. This means that by the time the delegate is executed, ie sometime after the end of the loop, the value of i is 6.

To capture a value, assign it to a variable declared in the loop body. On each iteration of the loop, a new instance will be created for each variable declared within it.

Jon Skeet's articles on closures has a deeper explanation and more examples.

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);
}
眼泪淡了忧伤 2024-08-27 21:57:19

当你点击任何按钮时,它们都已经从 1 到 7 生成了,所以它们都将表达 i 的最终状态,即 7。

By the time you click any button, they have all been generated from 1 thru 7, so they will all express the final state of i which is 7.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文