I apologize for the large amount of text. Skit read:

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. 

That is, if I understood correctly, in the end something like this is created:

 class <>G1 { int inside; // точнее, ссылка на inside <>G2 outside; } class <>G2 { int outside } 

And now the question (if I understood everything correctly) - why do this? Why can not be done simply:

 class <>G { int inside; // точнее, ссылка на inside int outside; } 

    1 answer 1

    Great question! See what's the matter.

    Here is the text:

     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(); } } 

    turns into a C # compiler like this (I changed the names for clarity; in fact, the C # compiler uses completely unreadable names with characters that are forbidden for you and me to avoid conflicts):

     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(); } } 

    (You can see the real code for the current version of the compiler at sharplab.io .)

    The fact is that variable references in .NET cannot be a class field (because an instance of a class can survive the context in which the variable is defined).

    Therefore, instead, all the “captured” variables are turned into fields of internal classes ( OuterScope / InnerScope ), and access to these variables turns into access to the fields of the object! These classes must correspond to the blocks inside the program: InnerScope is created every time you InnerScope for block, and accordingly, its fields are “visible” directly only inside this iteration. The outside variable must be the same and visible in the whole Main method, so it cannot be “shoved” into an InnerScope object.

    See you

    • Thank you very much! I read about references to variables in someone's blog and, apparently, misunderstood. I thought that this magic IL allows you to get a link to a local variable. Thank! - Veikedo
    • one
      You are welcome! In fact, I was a little deceived: the links actually exist ( ref int ), but they cannot be a class field. Eric Lippert explains why this is so . (Correct the answer.) - VladD pm
    • Just to be sure - if inside and outside were in the same scope, then would the generated class be the same? - Veikedo
    • @Veikedo: as I understand it, yes. - VladD