Страницы

Поиск по вопросам

понедельник, 8 октября 2018 г.

Реализация замыканий в C#

Прошу прощения за большое количество текста. У Скита вычитал: 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
Видите?

Комментариев нет:

Отправить комментарий