La risposta di Eric Lippert colpisce davvero il punto. Comunque sarebbe bello costruire un'immagine di come i frame di stack e le acquisizioni funzionino in generale. Per fare ciò aiuta a guardare un esempio leggermente più complesso.
ecco il codice cattura:
public class Scorekeeper {
int swish = 7;
public Action Counter(int start)
{
int count = 0;
Action counter =() => { count += start + swish; }
return counter;
}
}
e qui è quello che penso l'equivalente sarebbe (se siamo fortunati Eric Lippert sarà commentare se questo è in realtà corretto o meno):
private class Locals
{
public Locals(Scorekeeper sk, int st)
{
this.scorekeeper = sk;
this.start = st;
}
private Scorekeeper scorekeeper;
private int start;
public int count;
public void Anonymous()
{
this.count += start + scorekeeper.swish;
}
}
public class Scorekeeper {
int swish = 7;
public Action Counter(int start)
{
Locals locals = new Locals(this, start);
locals.count = 0;
Action counter = new Action(locals.Anonymous);
return counter;
}
}
Il punto è che la classe locale sostituisce l'intero frame dello stack e viene inizializzata di conseguenza ogni volta che viene richiamato il metodo Counter. In genere la cornice dello stack include un riferimento a "questo", oltre agli argomenti del metodo, oltre alle variabili locali. (Anche il frame di stack viene esteso quando viene inserito un blocco di controllo.)
Di conseguenza non abbiamo un solo oggetto corrispondente al contesto catturato, ma in realtà abbiamo un oggetto per ogni frame di stack catturato.
In base a questo, possiamo usare il seguente modello mentale: i frame di stack vengono mantenuti nell'heap (anziché nello stack), mentre lo stack stesso contiene solo i puntatori ai frame di stack presenti nell'heap. I metodi Lambda contengono un puntatore al frame dello stack. Questo viene fatto usando la memoria gestita, quindi il frame rimane nell'heap fino a quando non è più necessario.
Ovviamente il compilatore può implementare questo utilizzando solo l'heap quando l'oggetto heap è richiesto per supportare una chiusura lambda.
Quello che mi piace di questo modello è che fornisce un'immagine integrata per "rendimento". Possiamo pensare a un metodo iteratore (usando yield return) come se fosse stato creato lo stack frame sull'heap e il puntatore di riferimento memorizzato in una variabile locale nel chiamante, da usare durante l'iterazione.
fonte
2017-10-25 04:26:13
Ottima domanda. Non sono sicuro, ma sì, puoi mantenere il frame dello stack in C#. I generatori lo usano sempre (cosa LINQ per le strutture dati) che si basano sulla resa sotto il cofano. Spero di non essere fuori luogo. se lo sarò, imparerò molto. –
yield trasforma il metodo in una classe separata con una macchina a stati. Lo stack in sé non viene tenuto in giro, ma lo stato dello stack viene spostato nello stato di classe in una classe generata dal compilatore – thecoop
@thecoop, hai un link che spieghi questo per favore? –