#c_sharp
Прошу прощения за большое количество текста. У Скита вычитал:
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;
}
Ответы
Ответ 1
Отличный вопрос! Смотрите, в чём дело. Вот такой текст: 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. Видите?
Комментариев нет:
Отправить комментарий