2008-11-20 17 views
97

Esta es una extensión de la pregunta de Access to Modified Closure. Solo quiero verificar si lo siguiente es lo suficientemente seguro para el uso en producción.Acceso al cierre modificado (2)

List<string> lists = new List<string>(); 
//Code to retrieve lists from DB  
foreach (string list in lists) 
{ 
    Button btn = new Button(); 
    btn.Click += new EventHandler(delegate { MessageBox.Show(list); }); 
} 

Solo ejecuto lo anterior una vez por inicio. Por ahora parece funcionar bien. Como Jon ha mencionado sobre el resultado contrario a la intuición en algún caso. Entonces, ¿qué tengo que tener cuidado aquí? ¿Estará bien si la lista se ejecuta más de una vez?

+16

Enhorabuena, ahora es parte de la documentación de Resharper. http: //confluence.jetbrains.net/display/ReSharper/Access + to + modified + closure – Kongress

+1

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

Respuesta

154

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

+0

Si ese es el caso, la variable temporal permanecerá en la memoria hasta que la aplicación se cierre, como para servir al delegado, y no será aconsejable hacerlo para bucles muy grandes si la variable ocupa mucha memoria. ¿Estoy en lo cierto? – faulty

+1

Permanecerá en la memoria durante el tiempo que haya elementos (botones) con el evento. Hay una manera de anular la suscripción de delegados únicos, que añadiré a la publicación. –

+2

Pero para calificar en su punto: sí, las variables capturadas de hecho pueden aumentar el alcance de una variable. Debe tener cuidado de no capturar cosas que no esperaba ... –

Cuestiones relacionadas