在WPF按钮上单击事件时使用lambda表达式时出现奇怪的行为

时间:2009-02-19 19:01:55

标签: c# wpf events lambda

我的问题很难解释,因此我创建了一个示例来显示。

当显示以下示例中的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);
        }
    }
}

有谁知道出了什么问题?

3 个答案:

答案 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;