2013-04-25 11 views
16
var x = 1; 
    Func<int,int> f = y => x + y; 
    x = 2; 
    Console.WriteLine(f(1)); 

L'uscita è 3. Vorrei assumere è 2, secondo http://www.cs.cornell.edu/~clarkson/courses/csci4223/2013sp/lec/lec12.pdfHo pensato che C# abbia scope lessicale, ma perché questo esempio mostra un comportamento di scoping dinamico?

+0

Hai cambiato il valore di 'x' prima di richiamare' f'. Perché non ti aspetti di usare il valore attuale di 'x'? –

+6

Perché 4? forse hai pensato 2? –

+0

E perché 4? Vedo come potresti pensare f (1) = 2 ma dove avresti 4? – jure

risposta

21

C'è una sottigliezza riguardante scoping lessicale che PDF non pienamente spiegare. Il suo esempio ha in realtà due diverse variabili denominate x, non riassegna il valore del primo x (e in effetti i linguaggi funzionali potrebbero non consentire la mutazione).

C# è in ambito lessicale - cerca x al punto di definizione della lambda, non quando viene richiamato il delegato. Ma: x si risolve in una variabile, non in un valore e legge il valore della variabile al momento dell'invocazione.

Ecco un esempio più completo:

int InvokeIt(Func<int, int> f) 
{ 
    int x = 2; 
    return f(1); 
} 

Func<int, int> DefineIt() 
{ 
    int x = 1; 
    Func<int, int> d = (y => x + y); 
    x = 3; // <-- the PDF never does this 
    return d; 
} 

Console.WriteLine(InvokeIt(DefineIt())); 

Il lambda lega al xvariabile che esiste all'interno DefineIt. Il valore (x = 1) al momento della definizione è irrilevante. La variabile viene successivamente impostata su x = 3.

Ma chiaramente non è ambito dinamico, perché il x = 2 all'interno di InvokeIt non viene utilizzato.

+0

scusa, questo mi fa male al cervello, sto cercando di eseguire il tuo esempio per capire cosa sta succedendo, ma sto ricevendo un errore del compilatore che DefineIt() non restituisce un valore. non importa, l'hai aggiornato. – BlackICE

+0

@BlackICE: Mi dispiace, ora corretto. –

+0

ok, penso di capire come funziona con i diversi ambiti di x e il lambda che punta alla variabile, grazie Ben. – BlackICE

20

Questa domanda era the subject of my blog on the 20th of May 2013. Grazie per la bella domanda!


Stai fraintendendo cosa significhi "lessical scoped". Citiamo il documento che hai collegato a:

il corpo di una funzione viene valutato nel vecchio ambiente dinamico esistente al momento della definizione della funzione, non nell'ambiente corrente quando viene chiamata la funzione.

Ecco il codice:

int x = 1; 
Func<int,int> f = y => x + y; 
x = 2; 
Console.WriteLine(f(1)); 

Ora, ciò che è "l'ambiente dinamico che esiste al momento è stata definita la funzione"? Pensa a un "ambiente" come a una classe. Quella classe contiene un campo mutabile per ogni variabile. Quindi questa è la stessa:

Environment e = new Environment(); 
e.x = 1; 
Func<int,int> f = y => e.x + y; 
e.x = 2; 
Console.WriteLine(f(1)); 

Quando f viene valutata, x viene cercato nell'ambiente e che esisteva quando f è stato creato. Il contenuto di tale ambiente è stato modificato, ma l'ambiente che è f associato a è lo stesso ambiente. (Si noti che questo è in realtà il codice che il compilatore C# genera! Quando si utilizza una variabile locale in un lambda, il compilatore genera una speciale classe "ambiente" e trasforma ogni utilizzo del locale in un utilizzo di un campo.)

Lasciatemi fare un esempio di come sarebbe il mondo se C# fosse stato applicato dinamicamente.Si consideri il seguente:

class P 
{ 
    static void M() 
    { 
     int x = 1; 
     Func<int, int> f = y => x + y; 
     x = 2; 
     N(f); 
    } 
    static void N(Func<int, int> g) 
    { 
     int x = 3; 
     Console.WriteLine(g(100)); 
    } 
} 

Se C# è stata ambito dinamico allora questo sarebbe stampare "103", perché la valutazione g valuta f, e in una lingua ambito in modo dinamico, valutando f sarebbe cercare il valore di xnel contesto attuale. Nell'ambiente corrente è 3. Nell'ambiente esistente quando è stato creato f, x è 2. Ancora, il valore di x in quell'ambiente è stato modificato; come indicato dal documento, l'ambiente è un ambiente dinamico. Ma quale ambiente è rilevante non cambia.

La maggior parte delle lingue in questi giorni non ha un ambito dinamico, ma ce ne sono alcune. PostScript, ad esempio, il linguaggio che viene eseguito sulle stampanti, viene applicato in modo dinamico.

+0

Quindi stai dicendo che la chiusura di C# cattura le variabili come il suo ambiente invece dei valori delle variabili. È possibile che esista un linguaggio imperativo che è scopico lessicale, ma la sua chiusura acquisisce solo valori, e quindi restituisce 2 nell'esempio di SO? – colinfang

+1

@colinfang si.Nel nuovo C++ c'è una sintassi per la chiusura su entrambe le variabili e valori. –

+0

Hmm, il tipo di valore viene chiamato dopo che la variabile viene copiata in base al valore. Non capisco perché per una chiusura il tipo di valore non è progettato per semplicemente "catturato dal valore", proprio come in un metodo. Allo stesso modo per il tipo di riferimento catturato da una chiusura utilizzare "copia per riferimento". – colinfang

Problemi correlati