Trappola variabile esterna

Cos’è esattamente la trappola variabile esterna? Spiegazione ed esempi in C # sono apprezzati.

EDIT: Incorporando diktat di Jon Skeet 🙂

Eric Lippert sulla trappola variabile esterna

La “Outer Variable Trap” si verifica quando uno sviluppatore si aspetta che il valore di una variabile venga catturato da un’espressione lambda o da un delegato anonimo, quando in realtà la variabile viene catturata.

Esempio:

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

Possibile uscita n. 1:

 0 1 2 3 4 5 6 7 8 9 

Possibile uscita n. 2:

 10 10 10 10 10 10 10 10 10 10 

Se ti aspettavi l’uscita n. 1, sei caduto nella trappola variabile esterna. Ottieni l’output n. 2.

Difficoltà:

Dichiarare una “Variabile interna” da catturare ripetutamente invece della “Variabile esterna” che viene catturata solo una volta.

 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(); } 

Per maggiori dettagli, vedi anche il blog di Eric Lippert .

Qualcosa di simile a

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

Non fornirà i risultati che ti aspetti perché Contains non viene eseguito per ogni iterazione. Tuttavia, l’assegnazione di s ad una variabile temporanea all’interno del ciclo risolverà questo problema.

@dtb è corretto (grande +1), ma è importante notare che questo si applica solo se l’ambito della chiusura si estende al di fuori del ciclo. Per esempio:

 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); } } 

Questo stamperà:

  I: 1 partita: Bill
 partita i: 5: Bob
 partita i: 9: David 

ReSharper avviserà su “Accesso alla chiusura modificata”, che può essere tranquillamente ignorata in questo caso.

Vale la pena notare che questa trappola esisteva anche per i cicli foreach ma è stata modificata da C # 5.0, cioè all’interno di chiusure foreach loop ora si chiudono su una nuova copia della variabile loop ogni volta. Quindi il seguente codice:

 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()); 

Stampa 120 120 120 , ma 100 110 120 > = C # 5.0

Tuttavia, i loop si comportano ancora allo stesso modo.

Questo articolo che spiega il concetto di chiusure è utile:

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

Inoltre, questo articolo è davvero buono da un’implementazione C # più specifica:

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

Comunque, il tl; lr è che l’ambito variabile è altrettanto importante nelle espressioni anonime dei delegati o lambda come lo è in qualsiasi altra parte del codice, il comportamento non è altrettanto ovvio.