2012-03-05 23 views
6

Non riesco a capire come eseguire il ciclo di un elenco Action. Quando provo, finisco con i valori che sono gli stessi della precedente iterazione.In loop attraverso un elenco di azioni

Ecco il codice (esempio semplificato):

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

uscita:

ghi 
ghi 
ghi 

Perché è sempre selezionando l'elemento finale in strings quando si esegue l'azione?
E come posso raggiungere il risultato desiderato, che sarebbe:

abc 
def 
ghi 

risposta

13

La vostra azione è una chiusura, quindi accede str per sé, non è una copia di str:

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

Gah, hai vinto. Sapevo come risolverlo, ma non riuscivo a ricordare il motivo per cui. Chiusura! Ho bisogno di chiusura! +1 :) – Joshua

+1

@Joshua non è stato molto tempo fa quando ho imparato un po 'più a fondo :) ... questo potrebbe essere utile per ulteriori letture http://stackoverflow.com/questions/9412672/lambda-expressions-with -multithreading-in-c-sharp –

+0

Interessante, non l'ho mai capito. Grazie. – demoncodemonkey

3

Questa è una bella situazione complicata. La risposta breve è quello di creare una copia della variabile locale prima di assegnarlo alla chiusura:

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

Check out this article on closures per ulteriori informazioni.

3

Questo comportamento è condizionato da Closures.

La variabile che è presente nella vostra lambda è un riferimento e non valore di copia. Ciò significa che punta all'ultimo valore assunto da str, che è "ghi" nel tuo caso. Ecco perché per ogni chiamata va semplicemente allo stessa posizione di memoria e recupera, naturalmente, lo stesso valore.

Se si scrive il codice, come nelle risposte fornite, si forza un compilatore C# per rigenerare un nuovo valore ogni volta, in modo da un nuovo indirizzo verrà passato al labmda, così ogni lambda avrà è proprio variabile.

A proposito, se non mi sbaglio, C# squadra promessa di risolvere questo non naturale comportamento in C# 5.0. Quindi è meglio controllare il loro blog su questo argomento per futuri aggiornamenti.

+2

+1 buona spiegazione. Può valere la pena ricordare che Java lo fa nell'altro modo. Se si ha a che fare con 'Runnable' (in genere) per avviare un thread, le variabili vengono * copiate * nel nuovo contesto. Questo è anche il motivo per cui Java ti obbliga a renderli "definitivi". –