2012-01-13 4 views
5

Ho il seguente metodo:Perché C# non conserva il contesto per le chiamate anonime dei delegati?

static Random rr = new Random(); 
static void DoAction(Action a) 
{ 
    ThreadPool.QueueUserWorkItem(par => 
    { 
     Thread.Sleep(rr.Next(200)); 
     a.Invoke(); 
    }); 
} 

ora chiamare questo in un ciclo come questo:

for (int i = 0; i < 10; i++) 
{ 
    var x = i; 

    DoAction(() => 
    { 
     Console.WriteLine(i); // scenario 1 
     //Console.WriteLine(x); // scenario 2 
    }); 
} 

in scenario 1 dell'output sono: 10 10 10 10 ... 10
nello scenario 2 l'uscita è: 2 6 5 8 4 ... 0 (permutazione casuale da 0 a 9)

Come si spiega? C# non dovrebbe conservare le variabili (qui i) per la chiamata anonima del delegato?

+2

Ma preservando ('catturare') la * variabile * 'i' è * esattamente * cosa sta succedendo! Quello che * non * sta accadendo, sebbene tu lo voglia, sta preservando * il ** valore ** di 'i' nel momento in cui il delegato è impostato *. – AakashM

+2

Incidentalmente, ReSharper ti avviserà di un [Accesso alla chiusura modificata] (http://confluence.jetbrains.net/display/ReSharper/Access+to+modified+closure) quando viene presentato con questo codice; potresti trovare la spiegazione utile. – AakashM

+0

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx –

risposta

10

Il problema qui è che c'è una variabile i e dieci istanze/copie di x. Ogni lambda riceve un riferimento alla singola variabile i e una delle istanze di x. Ogni x viene scritto solo una volta e quindi ogni lambda vede l'unico valore che è stato scritto nel valore a cui fa riferimento.

La variabile i viene scritto fino a raggiungere 10. Nessuno dei lambda eseguito fino a quando il ciclo viene completata in modo che tutti vedono il valore finale di i quali è 10

Trovo questo esempio è un po 'più chiara se si riscriverlo come segue

int i = 0; // Single i for every iteration of the loop 
while (i < 10) { 
    int x = i; // New x for every iteration of the loop 
    DoAction(() => { 
    Console.WriteLine(i); 
    Console.WriteLine(x); 
    }); 
    i++; 
}; 
+1

In modo più puntuale, ogni 'x' non è solo scritto solo una volta, ma è un diverso 'x' poiché viene inizializzato ogni volta. Ci sono 10 'x' nella vita del ciclo e solo un' i'. –

1

Penso che si otterrà lo stesso risultato con Java o qualsiasi oggetto linguaggio orientato (non sono sicuro, ma qui sembra logico).

L'ambito di i è per l'intero ciclo e l'ambito di x è per ogni occorrenza.

Il resharper aiuta a individuare questo tipo di problema.

1

DoAction genera il thread e ritorna immediatamente. Quando il thread si risveglia dal suo sonno casuale, il ciclo sarà terminato e il valore di i sarà avanzato fino a 10. Il valore di x, d'altra parte, viene catturato e congelato prima della chiamata , quindi otterrete tutti i valori da 0 a 9 in ordine casuale, a seconda di quanto tempo ogni thread si addormenta in base al generatore di numeri casuali.

Problemi correlati