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?
It is because of how closures are evaluated compiler in the loop:
The compiler assumes that you will need the context of
a
in the closure, since you are usinga.Value
, so it is creating one lambda expression that uses the value ofa
. 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:To fix this do the following:
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 arrayn
, and it never gets touched after that. However, the lambda expression attached to the event handler, which returnsa.Value()
, hasn't actually been evaluated yet. As a result, when it does run, it'll then get the current value ofa
, 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: