Странное поведение при использовании лямбда-выражения в событии нажатия кнопок WPF - PullRequest
2 голосов
/ 19 февраля 2009

Мою проблему трудно объяснить, поэтому я создал пример для показа здесь.

Когда в приведенном ниже примере показано окно 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 ]

4 голосов
/ 19 февраля 2009

Это из-за того, как замыкания оцениваются компилятором в цикле:

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. Однако 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 голос
/ 19 февраля 2009

не интуитивно понятно, а? Причина в том, что лямбда-выражения захватывают переменные вне выражения.

Когда цикл for выполняется в последний раз, для переменной a устанавливается последний элемент в массиве n, и после этого он никогда не затрагивается. Однако лямбда-выражение, прикрепленное к обработчику событий, которое возвращает a.Value(), на самом деле еще не было оценено. В результате, когда он запустится, он получит текущее значение 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);
0 голосов
/ 19 февраля 2009

Чтобы исправить это, сделайте следующее:

var value = a.Value();
b.Click += (x, y) => value;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...