Avant C # 5, vous devez re-déclarer une variable à l' intérieur du foreach - sinon, elle est partagée et tous vos gestionnaires utiliseront la dernière chaîne:
foreach (string list in lists)
{
string tmp = list;
Button btn = new Button();
btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}
De manière significative, notez qu'à partir de C # 5, cela a changé, et en particulier dans le cas deforeach , vous n'avez plus besoin de le faire: le code de la question fonctionnerait comme prévu.
Pour montrer que cela ne fonctionne pas sans cette modification, tenez compte des éléments suivants:
string[] names = { "Fred", "Barney", "Betty", "Wilma" };
using (Form form = new Form())
{
foreach (string name in names)
{
Button btn = new Button();
btn.Text = name;
btn.Click += delegate
{
MessageBox.Show(form, name);
};
btn.Dock = DockStyle.Top;
form.Controls.Add(btn);
}
Application.Run(form);
}
Exécutez ce qui précède avant C # 5 , et bien que chaque bouton affiche un nom différent, cliquez sur les boutons pour afficher «Wilma» quatre fois.
En effet, la spécification du langage (ECMA 334 v4, 15.8.4) (avant C # 5) définit:
foreach (V v in x) embedded-statement est ensuite étendu à:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Notez que la variable v(qui est la vôtre list) est déclarée en dehors de la boucle. Ainsi, selon les règles des variables capturées, toutes les itérations de la liste partageront le détenteur de la variable capturée.
À partir de C # 5, cela est changé: la variable d'itération ( v) est portée à l' intérieur de la boucle. Je n'ai pas de référence de spécification, mais cela devient essentiellement:
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Re désabonnement; si vous souhaitez activement désinscrire un gestionnaire anonyme, l'astuce consiste à capturer le gestionnaire lui-même:
EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;
De même, si vous voulez un gestionnaire d'événements unique (tel que Load, etc.):
EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
// ... code
obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;
Ceci est maintenant auto-désabonnement ;-p