Самый простой способ узнать, как это реализовано, это попробовать. Напишите некоторый код, который использует захваченную переменную, скомпилируйте его, а затем посмотрите на него в Reflector . Обратите внимание, что записывается переменная , а не значение . Это одно из больших различий между Java и C # в этой области.
Основная идея заключается в том, что каждый уровень области действия, содержащий хотя бы одну захваченную переменную, приводит к созданию нового класса с полями для захваченных переменных. Если существует более одного уровня, то у внутренней области также есть поле для следующей области, и так далее. Подлинные локальные переменные в стеке становятся ссылками на экземпляры автоматически сгенерированных классов.
Вот пример:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Action> actions = new List<Action>();
for (int i=0; i < 5; i++)
{
int copyOfI = i;
for (int j=0; j < 5; j++)
{
int copyOfJ = j;
actions.Add(delegate
{
Console.WriteLine("{0} {1}", copyOfI, copyOfJ);
});
}
}
foreach (Action action in actions)
{
action();
}
}
}
(Вы получите другие результаты, если вы не берете копию, конечно - экспериментируйте!) Это скомпилировано в код, подобный следующему:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<Action> actions = new List<Action>();
for (int i=0; i < 5; i++)
{
OuterScope outer = new OuterScope();
outer.copyOfI = i;
for (int j=0; j < 5; j++)
{
InnerScope inner = new InnerScope();
inner.outer = outer;
inner.copyOfJ = j;
actions.Add(inner.Action);
}
}
foreach (Action action in actions)
{
action();
}
}
class OuterScope
{
public int copyOfI;
}
class InnerScope
{
public int copyOfJ;
public OuterScope outer;
public void Action()
{
Console.WriteLine("{0} {1}", outer.copyOfI, copyOfJ);
}
}
}
Каждая ссылка на захваченную переменную в конечном итоге проходит через экземпляр сгенерированного класса, поэтому это не просто одноразовая копия. (Хорошо, в этом случае ничто другое в коде не использует захваченные переменные, но вы можете легко представить, что это может.) Обратите внимание, что для любой итерации внешнего цикла все пять новых экземпляров совместно используют один экземпляр OuterScope
. Возможно, вы захотите попробовать поиграть с дополнительным кодом в делегате, чтобы увидеть, как это повлияет на ситуацию - если делегат изменится copyofI
, это изменение будет видно в следующем делегате; изменения в copyOfJ
не будут видны, потому что следующий делегат будет использовать отдельный экземпляр InnerScope
.