2009-02-19 3 views
2

Mon problème est difficile à expliquer, j'ai donc créé un exemple à montrer ici.Comportement étrange lors de l'utilisation de l'expression lambda sur les boutons WPF click event

Lorsque la fenêtre WPF de l'exemple ci-dessous est affichée, trois boutons sont affichés, chacun avec un texte différent. Quand un de ces boutons est cliqué, je suppose que son texte devrait être affiché dans le message, mais à la place, ils affichent tous le même message, comme si tous utilisaient le gestionnaire d'événements du dernier bouton.

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

Est-ce que quelqu'un sait ce qui ne va pas?

+0

double possible de [C# Capturé Variable Dans Boucle] (http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal

Répondre

4

Il est à cause de la façon dont les fermetures sont évaluées compilateur dans la boucle:

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

Le compilateur suppose que vous aurez besoin du contexte de a en la fermeture, puisque vous utilisez a.Value, donc il crée une expression lambda qui utilise la valeur de a. Cependant, a a une portée sur toute la boucle, donc il aura simplement la dernière valeur qui lui est assignée.

Pour contourner ce problème, vous devez copier a à une variable dans la boucle, puis l'utiliser:

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

Pour résoudre ce problème procédez comme suit:

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

non-intuitif, hein? La raison en est à cause de la façon dont les expressions lambda capturent des variables provenant de l'extérieur de l'expression. Lorsque la boucle for s'exécute pour la dernière fois, la variable a est définie sur le dernier élément du tableau n et elle n'est jamais touchée par la suite. Cependant, l'expression lambda attachée au gestionnaire d'événements, qui renvoie a.Value(), n'a pas encore été évaluée. Par conséquent, quand il s'exécute, il obtiendra alors la valeur actuelle de a, qui est alors le dernier élément.

La meilleure façon de faire ce travail la façon dont vous vous attendez à sans utiliser un tas de variables supplémentaires et ainsi de suite serait probablement changer quelque chose comme ceci:

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