C# 访问修改后的闭包 (2)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/304258/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 22:24:51  来源:igfitidea点击:

Access to Modified Closure (2)

c#.netresharperclosures

提问by faulty

This is an extension of question from Access to Modified Closure. I just want to verify if the following is actually safe enough for production use.

这是从Access 到 Modified Closure问题的扩展。我只想验证以下内容对于生产使用是否真的足够安全。

List<string> lists = new List<string>();
//Code to retrieve lists from DB    
foreach (string list in lists)
{
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(list); });
}

I only run through the above once per startup. For now it seems to work alright. As Jon has mentioned about counterintuitive result in some case. So what do I need to watch out here? Will it be ok if the list is run through more than once?

每次启动我只运行一次以上。现在它似乎工作正常。正如 Jon 在某些情况下提到的违反直觉的结果。那么我在这里需要注意什么?如果列表不止一次运行就可以了吗?

采纳答案by Marc Gravell

Prior to C# 5, you need to re-declare a variable insidethe foreach - otherwise it is shared, and all your handlers will use the last string:

到C#5之前,你需要重新声明一个变量里面在foreach -否则它是共享的,并且所有的处理程序将使用最后一个字符串:

foreach (string list in lists)
{
    string tmp = list;
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}

Significantly, note that from C# 5 onwards, this has changed, and specifically in the case of foreach, you do not need to do this any more: the code in the question would work as expected.

值得注意的是,请注意,从 C# 5 开始,这已经发生了变化,特别是在 的情况下foreach,您不再需要这样做:问题中的代码将按预期工作。

To show this not working without this change, consider the following:

要表明没有此更改就无法正常工作,请考虑以下事项:

string[] names = { "Fred", "Barney", "Betty", "Wilma" };
using (Form form = new Form())
{
    foreach (string name in names)
    {
        Button btn = new Button();
        btn.Text = name;
        btn.Click += delegate
        {
            MessageBox.Show(form, name);
        };
        btn.Dock = DockStyle.Top;
        form.Controls.Add(btn);
    }
    Application.Run(form);
}

Run the above prior to C# 5, and although each button shows a different name, clicking the buttons shows "Wilma" four times.

在 C# 5 之前运行上面的代码,虽然每个按钮显示不同的名称,但单击按钮会显示“Wilma”四次。

This is because the language spec (ECMA 334 v4, 15.8.4) (before C# 5) defines:

这是因为语言规范 (ECMA 334 v4, 15.8.4)(C# 5 之前)定义了:

foreach (V v in x)embedded-statementis then expanded to:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
         while (e.MoveNext()) {
            v = (V)(T)e.Current;
             embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

foreach (V v in x)embedded-statement然后扩展为:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
         while (e.MoveNext()) {
            v = (V)(T)e.Current;
             embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

Note that the variable v(which is your list) is declared outsideof the loop. So by the rules of captured variables, all iterations of the list will share the captured variable holder.

请注意,变量v(即您的list)是在循环之外声明的。所以根据捕获变量的规则,列表的所有迭代都将共享捕获的变量持有者。

From C# 5 onwards, this is changed: the iteration variable (v) is scoped insidethe loop. I don't have a specification reference, but it basically becomes:

从 C# 5 开始,这一点发生了变化:迭代变量 ( v) 的作用域循环内。我没有规范参考,但它基本上变成:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}


Re unsubscribing; if you actively want to unsubscribe an anonymous handler, the trick is to capture the handler itself:

重新退订;如果您想取消订阅匿名处理程序,诀窍是捕获处理程序本身:

EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;

Likewise, if you want a once-only event-handler (such as Load etc):

同样,如果您想要一个一次性的事件处理程序(例如 Load 等):

EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
  // ... code
  obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;

This is now self-unsubscribing ;-p

这是现在自我取消订阅;-p