W zamknięciu, co wyzwala nową instancję przechwyconej zmiennej?

Czytam książki Jona SkeetaC # w głębi.

Na stronie 156 ma przykład Listing 5.13 „Przechwytywanie wielu instancji zmiennych z wieloma delegatami”.

List<ThreadStart> list = new List<ThreadStart>();

for(int index=0; index < 5; index++;)
{
    int counter = index*10;
    list.Add(delegate
          {
              Console.WriteLine(counter);
              counter++;
          }
        );
}

foreach(ThreadStart t in list)
{
    t();
}

list[0]();
list[0]();
list[0]();

list[1]();

W wyjaśnieniu po tej liście mówi: „każda z instancji delegata przechwyciła w tym przypadku inną zmienną”.

Rozumiem to wystarczająco dobrze, ponieważ rozumiem, że za każdym razem, gdy zamykasz zmienną, kompilator generuje IL, który hermetyzuje ją w nowej klasie stworzonej specjalnie w celu umożliwienia przechwycenia tej zmiennej (zasadniczo czyniąc ją typem odniesienia, tak że wartość, do której się odnosi) nie zostanie zniszczony przez ramkę stosu aktualnie wykonanego zakresu).

Ale potem mówi o tym, co by się stało, gdybyśmy pojmaliindex bezpośrednio zamiast tworzyćcounter zmienna - „wszyscy delegaci mieliby tę samą zmienną”.

Tego nie rozumiem. Nie jestindex w takim samym zakresie jakcounter? Dlaczego kompilator również nie utworzył nowej instancjiindex dla każdego delegata?

Uwaga: Wydaje mi się, że wymyśliłem to, pisząc to pytanie, ale zostawię to pytanie dla potomności. Myślę, że odpowiedź jest takaindex jest w rzeczywistości w innym zakresie niżcounter. Indeks jest zasadniczo deklarowany „poza” pętlą for ... jest to ta sama zmienna za każdym razem.

Spojrzenie na IL wygenerowane dla afor oznacza, że ​​zmienne są deklarowane poza pętlą (length ii były zmienne zadeklarowane wfor deklaracja pętli).

.locals init (
    [0] int32 length,
    [1] int32 i,
    [2] bool CSRozumiem to wystarczająco dobrze, ponieważ rozumiem, że za każdym razem, gdy zamykasz zmienną, kompilator generuje IL, który hermetyzuje ją w nowej klasie stworzonej specjalnie w celu umożliwienia przechwycenia tej zmiennej (zasadniczo czyniąc ją typem odniesienia, tak że wartość, do której się odnosi) nie zostanie zniszczony przez ramkę stosu aktualnie wykonanego zakresu).0000
)

IL_0000: nop
IL_0001: ldc.i4.s 10
IL_0003: stloc.0
IL_0004: ldc.i4.0
IL_0005: stloc.1
IL_0006: br.s IL_001b
// loop start (head: IL_001b)
    IL_0008: nop
    IL_0009: ldloca.s i
    IL_000b: call instance string [mscorlib]System.Int32::ToString()
    IL_0010: call void [mscorlib]System.Console::WriteLine(string)
    IL_0015: nop
    IL_0016: nop
    IL_0017: ldloc.1
    IL_0018: ldc.i4.1
    IL_0019: add
    IL_001a: stloc.1

    IL_001b: ldloc.1
    IL_001c: ldloc.0
    IL_001d: clt
    IL_001f: stloc.2
    IL_0020: ldloc.2
    IL_0021: brtrue.s IL_0008
// end loop

Jedna rzecz, którą książka mogłaby zrobić lepiej w tym temacie, to wyjaśnienie, co robi kompilator, ponieważ cała ta „magia” ma sens, jeśli zrozumiesz, że kompilator zawija zmienną zamkniętą w nowej klasie.

Popraw wszelkie nieporozumienia lub nieporozumienia, jakie mogę mieć. Ponadto możesz rozwinąć i / lub dodać do mojego wyjaśnienia.

questionAnswers(2)

yourAnswerToTheQuestion