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

发布于 2024-07-13 17:32:45 字数 928 浏览 6 评论 0原文

我的问题很难解释,所以我创建了一个例子在这里展示。

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

有谁知道出了什么问题吗?

My problem is hard to explain, so I created an example to show here.

When the WPF window in the example below is shown, three buttons are displayed, each one with a different text.

When anyone of these buttons is clicked, I assume its text should be displayed in the message, but instead, all of them display the same message, as if all of them were using the event handler of the last button.

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

Does anyone know what is wrong?

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

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

发布评论

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

评论(3

走走停停 2024-07-20 17:32:45

这是因为编译器在循环中评估闭包的方式:

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

It is because of how closures are evaluated compiler in the loop:

foreach (var a in n) {
    Button b = new Button();
    b.Content = a.Key;
    b.Click += (x, y) => a.Value();
    stackPanel.Children.Add(b);
}

The compiler assumes that you will need the context of a in the closure, since you are using a.Value, so it is creating one lambda expression that uses the value of a. However, a has scope across the entire loop, so it will simply have the last value assigned to it.

To get around this, you need to copy a to a variable inside the loop and then use that:

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);
}
星星的轨迹 2024-07-20 17:32:45

不直观,是吧? 原因是 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);

Unintuitive, huh? The reason is because of how lambda expressions capture variables from outside the expression.

When the for loop runs for the last time, the variable a is set to the last element in the array n, and it never gets touched after that. However, the lambda expression attached to the event handler, which returns a.Value(), hasn't actually been evaluated yet. As a result, when it does run, it'll then get the current value of a, which by then is the last element.

The easiest way to make this work the way you expect it to without using a bunch of extra variables and so forth would probably to change to something like this:

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);
甜宝宝 2024-07-20 17:32:45

要解决此问题,请执行以下操作:

var value = a.Value();
b.Click += (x, y) => value;

To fix this do the following:

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