Antes de C # 5, debe volver a declarar una variable dentro de foreach; de lo contrario, se comparte y todos sus controladores usarán la última cadena:
foreach (string list in lists)
{
string tmp = list;
Button btn = new Button();
btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}
Significativamente, tenga en cuenta que desde C # 5 en adelante, esto ha cambiado, y específicamente en el caso deforeach , ya no necesita hacer esto: el código en la pregunta funcionaría como se esperaba.
Para mostrar que esto no funciona sin este cambio, considere lo siguiente:
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);
}
Ejecute lo anterior antes de C # 5 , y aunque cada botón muestra un nombre diferente, al hacer clic en los botones se muestra "Wilma" cuatro veces.
Esto se debe a que la especificación de lenguaje (ECMA 334 v4, 15.8.4) (antes de C # 5) define:
foreach (V v in x) embedded-statement luego se expande a:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Tenga en cuenta que la variable v(que es su list) se declara fuera del ciclo. Entonces, según las reglas de las variables capturadas, todas las iteraciones de la lista compartirán el titular de la variable capturada.
Desde C # 5 en adelante, esto se cambia: la variable de iteración ( v) tiene un alcance dentro del ciclo. No tengo una referencia de especificación, pero básicamente se convierte en:
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Volver a cancelar la suscripción; Si desea cancelar activamente la suscripción de un controlador anónimo, el truco consiste en capturar el controlador en sí:
EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;
Del mismo modo, si desea un controlador de eventos único (como Load, etc.):
EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
// ... code
obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;
Esto ahora es auto-cancelación de suscripción ;-p