Eric Lipperts Antwort trifft wirklich den Punkt. Es wäre jedoch nett, ein Bild davon zu erstellen, wie Stack-Frames und Captures im Allgemeinen funktionieren. Um dies zu tun, hilft es, ein etwas komplexeres Beispiel zu betrachten. Hier
ist das Erfassungscode:
public class Scorekeeper {
int swish = 7;
public Action Counter(int start)
{
int count = 0;
Action counter =() => { count += start + swish; }
return counter;
}
}
Und hier ist das, was ich denke, das Äquivalent wäre (wenn wir Glück haben Eric Lippert auf Kommentar wird, ob dies tatsächlich korrekt ist oder nicht):
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;
}
}
Der Punkt ist, dass die lokale Klasse den gesamten Stack-Frame ersetzt und jedes Mal, wenn die Counter-Methode aufgerufen wird, entsprechend initialisiert wird. In der Regel enthält der Stack-Frame einen Verweis auf 'this' sowie Methodenargumente und lokale Variablen. (Der Stapelrahmen wird auch tatsächlich erweitert, wenn ein Steuerblock eingegeben wird.)
Folglich haben wir nicht nur ein Objekt, das dem erfassten Kontext entspricht, stattdessen haben wir tatsächlich ein Objekt pro erfasstem Stapelrahmen.
Basierend darauf können wir das folgende mentale Modell verwenden: Stapelrahmen werden auf dem Heap (statt auf dem Stapel) gehalten, während der Stapel selbst nur Zeiger auf die Stapelrahmen enthält, die sich auf dem Heap befinden. Lambda-Methoden enthalten einen Zeiger auf den Stapelrahmen. Dies geschieht über verwalteten Speicher, so dass der Frame auf dem Heap bleibt, bis er nicht mehr benötigt wird.
Offensichtlich kann der Compiler dies implementieren, indem er nur den Heap verwendet, wenn das Heap-Objekt zur Unterstützung eines Lambda-Abschlusses benötigt wird.
Was ich an diesem Modell mag ist, dass es ein integriertes Bild für "Rendite" bietet. Wir können uns eine Iterator-Methode vorstellen (mit yield return), als wäre ihr Stack-Frame auf dem Heap und der referenzierende Pointer in einer lokalen Variablen im Aufrufer zur Verwendung während der Iteration erstellt worden.
Große Frage. Ich bin mir nicht sicher, aber ja, Sie können den Stapelrahmen in C# behalten. Generatoren verwenden es ständig (Sache LINQ für Datenstrukturen), die auf Ausbeute unter der Haube angewiesen sind. Hoffentlich bin ich nicht daneben. wenn ich bin, werde ich sehr viel lernen. –
Ausbeute macht die Methode in eine separate Klasse mit einer Zustandsmaschine. Der Stack selbst wird nicht beibehalten, aber der Stack-Status wird in eine vom Compiler generierte Klasse – thecoop
@thecoop in den Klassenstatus verschoben. Haben Sie einen Link, der das erklärt? –