Antes de C# 5, es necesario volver a declarar una variable dentro el foreach - de lo contrario, se comparte, y todos sus manipuladores usará 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 a partir de C# 5 en adelante, esto ha cambiado, y específicamente en el caso de foreach
, no necesita hacer esto más: el código en la pregunta funcionaría como se esperaba.
Para mostrar esto no funciona sin este cambio, tenga en cuenta 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);
}
Ejecutar lo anterior antes de C# 5, y aunque cada botón muestra un nombre diferente, haciendo clic en los botones de muestra "Wilma" cuatro veces.
Esto es porque la especificación de lenguaje (ECMA 334 v4, 15.8.4) (antes C# 5) define:
foreach (V v in x)
embedded-statement
continuación 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 de del bucle. Entonces, según las reglas de las variables capturadas, todas las iteraciones de la lista compartirán el titular de la variable capturada.
De C# 5 en adelante, esto se cambia: la variable de iteración (v
) tiene un ámbito dentro de el bucle. Yo 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
}
}
Re darse de baja; si quieres activamente para darse de baja un controlador en el anonimato, el truco consiste en capturar el manejador en sí:
EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;
Del mismo modo, si usted quiere una sola vez controlador de eventos (por ejemplo, carga, etc):
EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
// ... code
obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;
Esto ahora es autodesubscribing ;-p
Enhorabuena, ahora es parte de la documentación de Resharper. http: //confluence.jetbrains.net/display/ReSharper/Access + to + modified + closure – Kongress
Este fue complicado pero la explicación anterior lo dejó claro: _Esto puede parecer correcto, pero, de hecho, solo el último valor de la variable str será se usa cada vez que se hace clic en un botón. La razón para esto es que foreach se desenrolla en un ciclo while, pero la variable de iteración se define fuera de este ciclo. Esto significa que para cuando muestre el cuadro de mensaje, el valor de str ya puede haber sido iterado al último valor en la colección de cadenas. – DanielV