Trampa de Variable Exterior

¿Qué es exactamente la trampa de la variable externa? Explicación y ejemplos en C # son apreciados.

EDITAR: Incorporando el diktat de Jon Skeet 🙂

Eric Lippert en la trampa de la variable exterior

La “Trampa de Variable Externa” ocurre cuando un desarrollador espera que el valor de una variable sea capturado por una expresión lambda o un delegado anónimo, cuando en realidad la variable se captura por sí misma.

Ejemplo:

var actions = new List(); for (var i = 0; i < 10; i++) { actions.Add(() => Console.Write("{0} ", i)); } foreach (var action in actions) { action(); } 

Posible resultado n. ° 1:

 0 1 2 3 4 5 6 7 8 9 

Posible resultado n. ° 2:

 10 10 10 10 10 10 10 10 10 10 

Si esperaba la salida n. ° 1, ha caído en la trampa de la variable externa. Obtienes la salida n. ° 2.

Fijar:

Declara una “variable interna” que se capturará repetidamente en lugar de la “variable externa” que se captura solo una vez.

 var actions = new List(); for (var i = 0; i < 10; i++) { var j = i; actions.Add(() => Console.Write("{0} ", j)); } foreach (var action in actions) { action(); } 

Para más detalles, ver también el blog de Eric Lippert .

Algo como

 foreach (var s in strings) var x = results.Where(r => (r.Text).Contains(s)); 

No dará los resultados que espera porque los Contiene no se ejecutan para cada iteración. Sin embargo, la asignación de s a una variable temporal dentro del ciclo solucionará esto.

@dtb es correcto (gran +1), pero es importante tener en cuenta que esto solo se aplica si el scope del cierre se extiende fuera del ciclo. Por ejemplo:

 var objects = new [] { new { Name = "Bill", Id = 1 }, new { Name = "Bob", Id = 5 }, new { Name = "David", Id = 9 } }; for (var i = 0; i < 10; i++) { var match = objects.SingleOrDefault(x => x.Id == i); if (match != null) { Console.WriteLine("i: {0} match: {1}", i, match.Name); } } 

Esto se imprimirá:

  i: 1 partido: Bill
 i: 5 partido: Bob
 i: 9 partido: David 

ReSharper advertirá sobre “Acceso al cierre modificado”, que puede ignorarse de forma segura en este caso.

Vale la pena observar que esta trampa también existió para los bucles foreach pero se modificó desde C # 5.0, es decir, que los cierres de bucles foreach ahora se cierran sobre una nueva copia de la variable de bucle cada vez. Entonces el siguiente código:

 var values = new List() { 100, 110, 120 }; var funcs = new List>(); foreach (var v in values) funcs.Add(() => v); foreach (var f in funcs) Console.WriteLine(f()); 

Imprime 120 120 120 , pero 100 110 120 > = C # 5.0

Sin embargo, los bucles aún se comportan de la misma manera.

Este artículo que explica el concepto de cierres es útil:

http://en.wikipedia.org/wiki/Closure_(computer_science)

Además, este artículo es realmente bueno a partir de una implementación de C # más específica:

http://blogs.msdn.com/b/abhinaba/archive/2005/08/08/448939.aspx

De todos modos, el tl; lr es que el scope variable es tan importante en delegates anónimos o expresiones lambda como lo es en cualquier otro lugar dentro de tu código; el comportamiento simplemente no es tan obvio.