Prima di C # 5, è necessario dichiarare nuovamente una variabile all'interno di foreach, altrimenti è condivisa e tutti i gestori useranno l'ultima stringa:
foreach (string list in lists)
{
string tmp = list;
Button btn = new Button();
btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}
È significativo notare che da C # 5 in poi, questo è cambiato e, in particolare, nel caso diforeach
, non è più necessario farlo: il codice nella domanda funzionerebbe come previsto.
Per dimostrare che questo non funziona senza questa modifica, considera quanto segue:
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);
}
Eseguire quanto sopra prima di C # 5 e, sebbene ogni pulsante mostri un nome diverso, facendo clic sui pulsanti viene visualizzato "Wilma" quattro volte.
Questo perché la specifica della lingua (ECMA 334 v4, 15.8.4) (prima di C # 5) definisce:
foreach (V v in x)
embedded-statement
viene quindi espanso a:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Nota che la variabile v
(che è tua list
) è dichiarata al di fuori del ciclo. Quindi, secondo le regole delle variabili catturate, tutte le iterazioni della lista condivideranno il detentore della variabile catturata.
Da C # 5 in poi, questo è cambiato: la variabile di iterazione ( v
) ha l'ambito all'interno del ciclo. Non ho un riferimento alle specifiche, ma fondamentalmente diventa:
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Annullare nuovamente l'iscrizione; se vuoi attivamente annullare l'iscrizione a un gestore anonimo, il trucco è acquisire il gestore stesso:
EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;
Allo stesso modo, se vuoi un gestore di eventi una sola volta (come Load ecc.):
EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
// ... code
obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;
Questo è ora l'auto-annullamento dell'iscrizione ;-p