В заключение, что запускает новый экземпляр захваченной переменной?
Я читаю книгу Джона СкитаC # в глубине.
На странице 156 у него есть пример, Листинг 5.13 «Захват нескольких экземпляров переменных с несколькими делегатами».
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]();
В объяснении после этого листинга он говорит, что «в каждом случае экземпляры делегата захватывали разные переменные».
Я понимаю это достаточно хорошо, потому что я понимаю, что каждый раз, когда вы закрываете переменную, компилятор генерирует IL, который инкапсулирует его в новый класс, созданный специально для того, чтобы эта переменная была захвачена (по сути, делая ее ссылочным типом, чтобы значение, на которое он ссылается) чтобы не быть уничтоженным с фреймом стека текущей выполняемой области видимости).
Но затем он говорит о том, что случилось бы, если бы мы захватилиindex
непосредственно вместо созданияcounter
переменная - «все делегаты имели бы одну и ту же переменную».
Это я не понимаю. неindex
в том же объеме, что иcounter
? Почему бы компилятору не создавать новый экземплярindex
для каждого делегата?
Примечание: Я думаю, я понял это, когда набрал этот вопрос, но я оставлю этот вопрос здесь для потомков. Я думаю, что ответ таковindex
на самом деле в другом объеме, какcounter
, Индекс по существу объявляется "вне" цикла for ... это всегда одна и та же переменная.
Взгляните на IL, сгенерированный дляfor
цикл, это доказывает, что переменные объявлены вне цикла (length
а такжеi
переменные были объявлены вfor
объявление цикла).
.locals init (
[0] int32 length,
[1] int32 i,
[2] bool CSЯ понимаю это достаточно хорошо, потому что я понимаю, что каждый раз, когда вы закрываете переменную, компилятор генерирует IL, который инкапсулирует его в новый класс, созданный специально для того, чтобы эта переменная была захвачена (по сути, делая ее ссылочным типом, чтобы значение, на которое он ссылается) чтобы не быть уничтоженным с фреймом стека текущей выполняемой области видимости).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
Одна вещь, я думаю, что книга могла бы быть лучше в этом вопросе, это действительно объяснить, что делает компилятор, потому что вся эта «магия» имеет смысл, если вы понимаете, что компилятор переносит закрытую переменную в новый класс.
Пожалуйста, исправьте любые заблуждения или недоразумения, которые у меня могут быть. Кроме того, не стесняйтесь уточнять и / или дополнять мое объяснение.