La réponse d'Eric Lippert va droit au but. Cependant, il serait bon de se faire une idée de la façon dont les cadres de pile et les captures fonctionnent en général. Pour ce faire, il est utile d'examiner un exemple légèrement plus complexe.
Voici le code de capture :
public class Scorekeeper {
int swish = 7;
public Action Counter(int start)
{
int count = 0;
Action counter = () => { count += start + swish; }
return counter;
}
}
Et voici ce que je pense être l'équivalent (si nous avons de la chance, Eric Lippert nous dira si cela est correct ou non) :
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;
}
}
Le fait est que la classe locale se substitue à l'ensemble de la pile et est initialisée en conséquence chaque fois que la méthode Counter est invoquée. En général, le cadre de la pile comprend une référence à 'this', plus les arguments de la méthode, plus les variables locales. (Le cadre de la pile est aussi en fait étendu lorsqu'un bloc de contrôle est entré).
Par conséquent, nous ne disposons pas d'un seul objet correspondant au contexte capturé, mais plutôt d'un objet par trame de pile capturée.
Sur cette base, nous pouvons utiliser le modèle mental suivant : les trames de pile sont conservées sur le tas (au lieu d'être sur la pile), tandis que la pile elle-même ne contient que des pointeurs vers les trames de pile qui sont sur le tas. Les méthodes lambda contiennent un pointeur vers le cadre de pile. Ceci est fait en utilisant de la mémoire gérée, de sorte que le cadre reste sur le tas jusqu'à ce qu'il ne soit plus nécessaire.
Évidemment, le compilateur peut implémenter ceci en n'utilisant le tas que lorsque l'objet tas est nécessaire pour supporter une fermeture lambda.
Ce que j'aime dans ce modèle, c'est qu'il fournit une image intégrée du "rendement". Nous pouvons penser à une méthode d'itérateur (utilisant le rendement de retour) comme si son cadre de pile était créé sur le tas et le pointeur de référencement stocké dans une variable locale dans l'appelant, pour être utilisé pendant l'itération.