Прошу прощения за большое количество текста. У Скита вычитал:
Methodlnvoker[] delegates = new Methodlnvoker[2];
int outside =0; // #1 Создает экземпляр переменной только однажды
for (int i = 0; i < 2; i++)
{
int inside =0; // #2 Создает экземпляр переменной многократно
delegates[i] = delegate // #3 Захват переменной анонимным методом
{
Console.WriteLine ( " ({0 } ,{1}) " , outside, inside);
outside++;
inside++;
};
}
Methodlnvoker first = delegates[0] ;
Methodlnvoker second = delegates[1];
first () ;
first () ;
first () ;
second();
second();
Давайте подумаем, как это реализовано, по крайней мере, с компилятором С# 2 от
Microsoft. Происходит вот что: один дополнительный класс создается для содержания
переменной outer, а другой — для содержания переменной inner и ссылки на первый
дополнительный класс. По существу, каждая область видимости, которая содержит
захваченную переменную, получает собственный тип со ссылкой на следующую область
видимости, которая содержит захваченную переменную. В данном случае было два
экземпляра типа для содержания переменной inner, и оба они ссылаются на тот же
экземпляр типа, содержащий переменную outer.
То есть, если я правильно понял, в итоге создается нечто подобное:
class <>G1
{
int inside; // точнее, ссылка на inside
<>G2 outside;
}
class <>G2
{
int outside
}
А теперь вопрос (если я правильно все понял) - зачем так делать? Почему нельзя сделать просто:
class <>G
{
int inside; // точнее, ссылка на inside
int outside;
}
Ответ
Отличный вопрос! Смотрите, в чём дело.
Вот такой текст:
class Program
{
delegate void MethodInvoker();
static void Main(string[] args)
{
MethodInvoker[] delegates = new MethodInvoker[2];
int outside = 0; // #1 Создает экземпляр переменной только однажды
for (int i = 0; i < 2; i++)
{
int inside = 0; // #2 Создает экземпляр переменной многократно
delegates[i] = delegate // #3 Захват переменной анонимным методом
{
Console.WriteLine("({0}, {1})", outside, inside);
outside++;
inside++;
};
}
MethodInvoker first = delegates[0];
MethodInvoker second = delegates[1];
first();
first();
first();
second();
second();
}
}
превращается компилятором C# в такой (я изменил имена для ясности; на самом деле, компилятор C# использует совершенно нечитаемые имена с запрещёнными для нас с вами символами для того, чтобы не было конфликтов):
internal class Program
{
private delegate void MethodInvoker();
[CompilerGenerated]
private sealed class OuterScope
{
public int outside;
}
[CompilerGenerated]
private sealed class InnerScope
{
public OuterScope outerLocals;
public int inside;
public void Method()
{
Console.WriteLine("({0}, {1})", outerLocals.outside, inside);
outerLocals.outside++;
inside++;
}
}
private static void Main(string[] args)
{
// инициализация фрейма и его переменных
OuterScope outerScope = new OuterScope();
MethodInvoker[] delegates = new MethodInvoker[2];
outerScope.outside = 0; // было: outside = 0;
for (int i = 0; i < 2; i++)
{
// инициализация фрейма и его переменных
InnerScope innerScope = new InnerScope();
innerScope.outerLocals = outerScope;
innerScope.inside = 0; // было: inside = 0
delegates[i] = innerScope.Method;
}
MethodInvoker first = delegates[0];
MethodInvoker second = delegates[1];
first();
first();
first();
second();
second();
}
}
(Посмотреть реальный код для текущей версии компилятора можно на sharplab.io.)
Дело в том, что ссылки на переменные в .NET не могут быть полем класса (потому что экземпляр класса может пережить контекст, в котором определена переменная).
Поэтому вместо этого все «захваченные» переменные превращаются в поля внутренних классов (OuterScope/InnerScope), а доступ к этим переменных превращается в доступ к полям объекта! Эти классы должны соответствовать блокам внутри программы: InnerScope создаётся каждый раз при входе в блок for, соответственно этому и его поля «видны» непосредственно только внутри этой итерации. Переменная outside должна быть одна и та же, и видна во всём методе Main, поэтому её нельзя «впихнуть» в объект InnerScope
Видите?
Комментариев нет:
Отправить комментарий