2009-12-18 4 views
8

In questo esempio, sto tentando di passare per valore, ma il riferimento è passato invece.Chiusure C#, perché la variabile del ciclo è catturata per riferimento?

for (int i = 0; i < 10; i++) 
{ 
    Thread t = new Thread(() => new PhoneJobTest(i); 
    t.Start(); 
} 

Questo si può rimediare in questo modo:

for (int i = 0; i < 10; i++) 
{ 
    int jobNum = i; 
    Thread t = new Thread(() => new PhoneJobTest(jobNum); 
    t.Start(); 
} 

Cosa c'è sta succedendo qui? Perché l'esempio originale supera il riferimento?

+0

http://stackoverflow.com/questions/1095707/what-is-the-exact-definition-of-a-closure/ 1095770 # 1095770 –

+2

Penso che manchi una parentesi di coppia. –

+0

Ecco perché amo la sintassi lambda C++ 11 ... –

risposta

16

Bene, è proprio così che funziona C#. L'espressione lambda nell'istruzione costruisce una chiusura lessicale, che memorizza un singolo riferimento a i che persiste anche dopo la conclusione del ciclo.

Per rimediare, puoi fare solo la cosa che hai fatto.

Sentitevi liberi di leggere di più su questo particolare problema in tutto il Web; la mia scelta sarebbe Eric Lippert's discussion here.

+0

+1, buona domanda, buona risposta, buon collegamento. Grazie –

1

Succede a causa del modo in cui C# passa i parametri a un lambda. Racchiude l'accesso alle variabili in una classe creata durante la compilazione e la espone come un campo al corpo lambda.

2

Sicuramente si desidera leggere Eric Lippert di "Chiusura sopra la variabile del ciclo considerato nocivo":

In breve: Il comportamento che si vede è esattamente come # opere C .

16

Questo è più facile da capire se si guarda a ciò che accade, in termini di portata:

for (int i = 0; i < 10; i++) 
{ 
    Thread t = new Thread(() => new PhoneJobTest(i);  
    t.Start(); 
} 

traduce sostanza a qualcosa di molto vicino a questo:

int i = 0; 
while (i < 10) 
{ 
    Thread t = new Thread(() => new PhoneJobTest(i);  
    t.Start(); 
    i++; 
} 

Quando si utilizza un'espressione lambda , e usa una variabile dichiarata al di fuori della lambda (nel tuo caso, i), il compilatore crea qualcosa chiamato chiusura: una classe temporanea che "avvolge" la variabile i e la fornisce al delegato generato dal lambda.

La chiusura è costruito allo stesso livello come variabile (i), così nel tuo caso:

int i = 0; 
ClosureClass = new ClosureClass(ref i); // Defined here! (of course, not called this) 
while (i < 10) 
{ 
    Thread t = new Thread(() => new PhoneJobTest(i);  
    t.Start(); 
    i++; 
} 

A causa di questo, ogni thread ha l'stessa chiusura definito.

Quando si rielaborare il ciclo di utilizzare una temporanea, la chiusura viene generato a quel livello, invece:

for (int i = 0; i < 10; i++) 
{ 
    int jobNum = i; 
    ClosureClass = new ClosureClass(ref jobNum); // Defined here! 
    Thread t = new Thread(() => new PhoneJobTest(jobNum);  
    t.Start(); 
} 

Ora, ogni thread ottiene una propria istanza, e tutto funziona correttamente.

+2

Grazie a Reed una grande spiegazione visiva che la chiusura viene creata al livello in cui la variabile originale è stata istanziata –

0

Quando si utilizza un delegato anonimo o un'espressione lambda viene creato uno closure in modo che le variabili esterne possano essere referenziate. Quando viene creata la chiusura, le variabili dello stack (valore) vengono promosse all'heap.

Un modo per evitare ciò è avviare il thread con un delegato ParameterizedThreadStart. EG:

 static void Main() 
    { 

     for (int i = 0; i < 10; i++) 
     { 
      bool flag = false; 

      var parameterizedThread = new Thread(ParameterizedDisplayIt); 
      parameterizedThread.Start(flag); 

      flag = true; 
     } 

     Console.ReadKey(); 
    } 

    private static void ParameterizedDisplayIt(object flag) 
    { 
     Console.WriteLine("Param:{0}", flag); 
    } 

Casualmente mi sono imbattuto in questo concetto proprio ieri: Link

Problemi correlati