2012-03-05 38 views
6

No puedo entender cómo recorrer una lista Action. Cuando lo intento, termino con los valores siendo los mismos que la iteración anterior.Looping a través de una lista de acciones

Aquí está el código (ejemplo simplificado):

string[] strings = { "abc", "def", "ghi" }; 

var actions = new List<Action>(); 
foreach (string str in strings) 
    actions.Add(new Action(() => { Trace.WriteLine(str); })); 

foreach (var action in actions) 
    action(); 

Salida:

ghi 
ghi 
ghi 

¿Por qué es siempre seleccionando el elemento final en strings cuando se realiza la acción?
Y cómo puedo conseguir el resultado deseado, que sería:

abc 
def 
ghi 

Respuesta

13

Su acción es un cierre, por lo que accede a str sí mismo, no una copia de str:

foreach (string str in strings) 
{ 
    var copy = str; // this will do the job 
    actions.Add(new Action(() => { Trace.WriteLine(copy); })); 
} 
+0

Gah, usted gana. Sabía cómo solucionarlo, pero no podía recordar el motivo. ¡Cierre! ¡Necesito un cierre! +1 :) – Joshua

+1

@Joshua no fue hace mucho tiempo cuando aprendí en un poco más profundo :) ... esto puede ser bueno para leer más http://stackoverflow.com/questions/9412672/lambda-expressions-with -multithreading-in-c-sharp –

+0

Interesante, nunca me di cuenta. Gracias. – demoncodemonkey

3

Esta es una bonita situación complicada. La respuesta corta es crear una copia de la variable local antes de asignarlo al cierre:

string copy = str; 
actions.Add(new Action(() => { Trace.WriteLine(copy); })); 

Check out this article on closures para más información.

3

Este comportamiento está condicionado por Closures.

La variable que está presente en su lambda es una referencia y no copia valor. Eso significa que apunta al último valor asumido por str, que es "ghi" en su caso. Es por eso que para cada llamada simplemente va a la misma ubicación de memoria y recupera, naturalmente, el mismo valor.

Si se escribe el código, al igual que en las respuestas proporcionadas, se fuerza un compilador C# para regenerar un nuevo valor cada vez, por lo que una nueva dirección se pasará a la labmda, por lo que cada lambda tendrá su propia variable.

Por cierto, si no me equivoco, C# promesa equipo para solucionar este no comportamiento natural en C# 5.0. Por lo tanto, es mejor consultar su blog sobre este tema para futuras actualizaciones.

+2

+1 buena explicación. Vale la pena mencionar que Java lo hace de otra manera. Si se trata de 'Runnable' (típicamente) para iniciar un hilo, las variables * se copian * en el nuevo contexto. Esta es también la razón por la cual Java te obliga a convertirlos en "finales". –

Cuestiones relacionadas