Ответ Эрика Липперта действительно суть. Однако было бы неплохо составить представление о том, как стеки кадров и захваты работают в целом. Для этого полезно взглянуть на чуть более сложный пример.
Вот код захвата:
public class Scorekeeper {
int swish = 7;
public Action Counter(int start)
{
int count = 0;
Action counter = () => { count += start + swish; }
return counter;
}
}
И вот что я думаю, что эквивалент будет (если нам повезет, Эрик Липперт прокомментирует, действительно ли это правильно или нет):
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;
}
}
Дело в том, что локальный класс заменяет весь кадр стека и инициализируется соответственно каждый раз, когда вызывается метод Counter. Обычно фрейм стека содержит ссылку на this, аргументы метода и локальные переменные. (Кадр стека также в действительности расширяется при вводе блока управления.)
Следовательно, у нас нет только одного объекта, соответствующего захваченному контексту, вместо этого у нас фактически есть один объект на кадр захваченного стека.
Исходя из этого, мы можем использовать следующую ментальную модель: кадры стека хранятся в куче (а не в стеке), тогда как сам стек просто содержит указатели на кадры стека, находящиеся в куче. Лямбда-методы содержат указатель на кадр стека. Это делается с использованием управляемой памяти, поэтому кадр остается в куче, пока он больше не нужен.
Очевидно, что компилятор может реализовать это, используя кучу, только когда объект кучи необходим для поддержки лямбда-замыкания.
Что мне нравится в этой модели, так это то, что она обеспечивает интегрированную картину «доходности». Мы можем думать о методе итератора (с использованием yield return), как если бы его стек был создан в куче, а ссылочный указатель сохранен в локальной переменной вызывающей программы для использования во время итерации.