【问题标题】:Closures in C# event handler delegates? [duplicate]C# 事件处理程序委托中的闭包? [复制]
【发布时间】: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。

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

【问题讨论】:

标签: c# delegates anonymous-methods closures


【解决方案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。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-12
        • 1970-01-01
        相关资源
        最近更新 更多