我的问题很难解释,因此我创建了一个示例来显示。
当显示以下示例中的WPF窗口时,会显示三个按钮,每个按钮都有不同的文本。
当点击这些按钮中的任何一个时,我认为它的文本应该显示在消息中,而是所有这些按钮都显示相同的消息,就好像它们都使用了最后一个按钮的事件处理程序一样。
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
var stackPanel = new StackPanel();
this.Content = stackPanel;
var n = new KeyValuePair<string, Action>[] {
new KeyValuePair<string, Action>("I", () => MessageBox.Show("I")),
new KeyValuePair<string, Action>("II", () => MessageBox.Show("II")),
new KeyValuePair<string, Action>("III", () => MessageBox.Show("III"))
};
foreach (var a in n) {
Button b = new Button();
b.Content = a.Key;
b.Click += (x, y) => a.Value();
stackPanel.Children.Add(b);
}
}
}
有谁知道出了什么问题?
答案 0 :(得分:4)
这是因为在循环中如何评估闭包的编译器:
foreach (var a in n) {
Button b = new Button();
b.Content = a.Key;
b.Click += (x, y) => a.Value();
stackPanel.Children.Add(b);
}
编译器假定您在闭包中需要a
的上下文,因为您正在使用a.Value
,因此它创建了一个使用a
值的lambda表达式。但是,a
在整个循环中具有范围,因此它只会为其分配最后一个值。
要解决此问题,您需要将a
复制到循环内的变量,然后使用它:
foreach (var a in n) {
Button b = new Button();
b.Content = a.Key;
// Assign a to another reference.
var a2 = a;
// Set click handler with new reference.
b.Click += (x, y) => a2.Value();
stackPanel.Children.Add(b);
}
答案 1 :(得分:1)
不直观,是吗?原因在于lambda表达式如何从表达式外部捕获变量。
当for循环最后一次运行时,变量a
被设置为数组n
中的最后一个元素,之后永远不会被触及。但是,附加到事件处理程序(返回a.Value()
)的lambda表达式实际上尚未进行评估。结果,当它运行时,它将获得当前值a
,然后它是最后一个元素。
在不使用大量额外变量的情况下,使这项工作按预期方式运行的最简单方法可能会改为:
var buttons = n.Select(a => {
Button freshButton = new Button { Content = a.Key };
freshButton.Click += (x, y) => a.Value();
return freshButton;
});
foreach(var button in buttons)
stackPanel.Children.Add(button);
答案 2 :(得分:0)
要解决此问题,请执行以下操作:
var value = a.Value();
b.Click += (x, y) => value;