2010-08-05 9 views
45

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

EDIT: La incorporación de los dictados de Jon Skeet :)

Eric Lippert on the Outer Variable Trap

+3

No tenía idea de qué hablabas hasta que lo busqué en Google; Al hacerlo, encontré toneladas de explicaciones y ejemplos (en C#), ¿qué más estás buscando? – Marc

+2

@Marc Tal vez OP es una de esas personas (hay más seguro, al menos uno lo dijo explícitamente muchas veces) que quiere que SO tenga una respuesta para cada posible pregunta de programación relevante. La respuesta para este aparentemente desapareció. –

+1

@Maciej, excelente. ¡La lista maestra está un paso más cerca de completarse! Dominio de la web, ¡aquí vamos! – Marc

Respuesta

61

la "trampa variable exterior" ocurre cuando un desarrollador espera que el valor de una variable a ser capturado por una expresión lambda o delegado en el anonimato, cuando en realidad la variable se captura por sí misma.

Ejemplo:

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

salida Posible # 1:

0 1 2 3 4 5 6 7 8 9 

Posible salida # 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.

Fix:

Declare una "Variable interior" para ser capturado varias veces en lugar de la "Variable exterior" que es capturado sólo una vez.

var actions = new List<Action>(); 
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, véase también Eric Lippert's blog.

+1

juego de palabras interesante sobre la palabra "trampa": la variable es capturada (atrapado), y te atrapa un problema (caído en una trampa) –

+4

Dado que es probable que esta sea la respuesta aceptada, ¿hay alguna posibilidad de que agregues un enlace a la publicación de blog de Eric Lippert? http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx –

+0

Así que j es básicamente una variable 'fresca', y por lo tanto la acción k-th contiene una variable j_k ligada al valor k-ésimo asumido por la variable de bucle. Como resultado, se obtiene el comportamiento esperado. –

4

Algo así como

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

no dará los resultados que usted está esperando, porque la contiene no se ejecuta para cada iteración. Sin embargo, la asignación de s a una variable temporal dentro del ciclo solucionará esto.

+0

No estoy familiarizado con la sintaxis 'var =', ¿qué hace eso? = P – Marc

+0

Nitpick: 'Contiene' se ejecuta para cada iteración, pero' s' sorprenderá siempre con el mismo valor. – dtb

+0

@dtb @Marc Sí, ¿qué dtb dijo? Probablemente no lo explique tan bien. var es solo una alternativa para declarar explícitamente el tipo de devolución (en código real declararía realmente el tipo de devolución aquí, pero no estoy frente a un IDE y no tenía ganas de buscar lo que realmente devuelve ... creo es IEnumberable <>) – heisenberg

1

@dtb es correcto (gran +1), pero es importante tener en cuenta que esto solo se aplica si el alcance del cierre se extiende fuera del bucle. 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 imprimirá:

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

ReSharper advertirán sobre "Acceso al cierre modificado", que puede ser ignorado con seguridad en este caso.

0

En este artículo se explica el concepto de cierres es útil:

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

Además, este artículo es muy bueno desde un C# aplicación 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 ámbito variable es tan importante en delegados anónimos o expresiones lambda como en cualquier otro lugar dentro de tu código; el comportamiento simplemente no es tan obvio.

0

Es digno de notar que esta trampa existía para foreach bucles también, pero has been changed ya que C# 5.0, es decir, dentro de foreach bucles cierres ahora cerca de más de una copia nueva de la variable de bucle cada vez. Por lo que el código de abajo:

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

imprime 120 120 120< C# 5.0, pero 100 110 120> = C# 5.0

Sin embargo for bucles todavía se comportan de la misma manera.

Cuestiones relacionadas