metodos funciones eventos delegate delegados delegado comandos anonimos anonimas c# delegates anonymous-methods closures

eventos - funciones anonimas c#



¿Cierres en los delegados del controlador de eventos C#? (5)

El cierre captura la variable no el valor. Esto significa que en el momento en que se ejecuta el delegado, es decir, en algún momento después del final del bucle, el valor de i es 6.

Para capturar un valor, asignarlo a una variable declarada en el cuerpo del bucle. En cada iteración del bucle, se creará una nueva instancia para cada variable declarada dentro de él.

Los artículos de Jon Skeet sobre cierres tienen una explicación más profunda y más ejemplos.

for (int i = 0; i < 7; i++) { var copy = i; Button newButton = new Button(); newButton.Text = "Click me!"; newButton.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("I am button number " + copy); }; this.Controls.Add(newButton); }

Esta pregunta ya tiene una respuesta aquí:

Vengo de un fondo de programación funcional en este momento, así que perdóneme si no entiendo los cierres en C #.

Tengo el siguiente código para generar dinámicamente botones que obtienen controladores de eventos anónimos:

for (int i = 0; i < 7; i++) { Button newButton = new Button(); newButton.Text = "Click me!"; newButton.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("I am button number " + i); }; this.Controls.Add(newButton); }

Esperaba que el texto "I am button number " + i se cerrara con el valor de i en esa iteración del bucle for. Sin embargo, cuando ejecuto el programa, cada botón dice I am button number 7 . ¿Qué me estoy perdiendo? Estoy usando VS2005.

Edit: Así que supongo que mi siguiente pregunta es, ¿cómo capturo el valor?


En el momento en que hace clic en cualquier botón, todos se han generado de 1 a 7, por lo que todos expresarán el estado final de i, que es 7.


Ha creado siete delegados, pero cada delegado tiene una referencia a la misma instancia de i .

La función MessageBox.Show solo se llama cuando se hace clic en el botón . Para cuando el botón ha hecho clic, el bucle se ha completado. Entonces, en este punto seré igual a siete.

Prueba esto:

for (int i = 0; i < 7; i++) { Button newButton = new Button(); newButton.Text = "Click me!"; int iCopy = i; // There will be a new instance of this created each iteration newButton.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("I am button number " + iCopy); }; this.Controls.Add(newButton); }


Nick tiene razón, pero quería explicar un poco mejor en el texto de esta pregunta exactamente por qué .

El problema no es el cierre; es el for-loop. El bucle solo crea una variable "i" para todo el bucle. No crea una nueva variable "i" para cada iteración. Nota: esto ha cambiado para C # 5.

Esto significa que cuando su delegado anónimo captura o se cierra sobre esa variable "i" se está cerrando sobre una variable que es compartida por todos los botones. Para cuando realmente haga clic en cualquiera de esos botones, el bucle ya habrá terminado de incrementar esa variable hasta 7.

Lo único que podría hacer de manera diferente al código de Nick es usar una cadena para la variable interna y construir todas esas cadenas en el frente en lugar de hacerlo al presionar un botón, así:

for (int i = 0; i < 7; i++) { var message = string.Format("I am button number {0}.", i); Button newButton = new Button(); newButton.Text = "Click me!"; newButton.Click += delegate(Object sender, EventArgs e) { MessageBox.Show(message); }; this.Controls.Add(newButton); }

Eso solo intercambia un poco de memoria (reteniendo variables de cadena más grandes en lugar de números enteros) por un poco de tiempo de CPU más adelante ... depende de su aplicación lo que importa más.

Otra opción es no codificar manualmente el bucle en absoluto:

this.Controls.AddRange(Enumerable.Range(0,7).Select(i => { var b = new Button() {Text = "Click me!", Top = i * 20}; b.Click += (s,e) => MessageBox.Show(string.Format("I am button number {0}.", i)); return b; }).ToArray());

Me gusta esta última opción, no tanto porque elimina el bucle sino porque comienza a pensar en términos de compilación de estos controles a partir de una fuente de datos.


Para obtener este comportamiento, necesita copiar la variable localmente, no usar el iterador:

for (int i = 0; i < 7; i++) { var inneri = i; Button newButton = new Button(); newButton.Text = "Click me!"; newButton.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("I am button number " + inneri); }; this.Controls.Add(newButton); }

El razonamiento se discute con mucho mayor detalle en esta pregunta .