No encerramento, o que desencadeia uma nova instância da variável capturada?
Estou lendo o Jon Skeet'sC # em profundidade.
Na página 156, ele tem um exemplo, a Listagem 5.13 "Capturando Instanciações de Variáveis Múltiplas com Vários Delegados".
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]();
Na explicação após esta listagem, ele diz que "cada uma das instâncias delegadas capturou uma variável diferente neste caso".
Eu entendo isso bem o suficiente porque eu entendo que cada vez que você fecha uma variável o compilador gera IL que o encapsula em uma nova classe feita especificamente para permitir que aquela variável seja capturada (essencialmente tornando-a um tipo de referência para que o valor esteja se referindo to não é destruído com o quadro de pilha do escopo atualmente em execução).
Mas então ele fala sobre o que teria acontecido se tivéssemos capturadoindex
diretamente em vez de criar ocounter
variável - "todos os delegados teriam compartilhado a mesma variável".
Isso eu não entendo. Não éindex
no mesmo âmbito quecounter
? Por que o compilador não criaria também uma nova instância deindex
para cada delegado?
Nota: Eu acho que descobri como eu digitei essa questão, mas deixarei a questão aqui para a posteridade. Eu acho que a resposta é queindex
está realmente em um escopo diferente comocounter
. O índice é essencialmente declarado "fora" do loop for ... é a mesma variável todas as vezes.
Dando uma olhada no IL gerado por umfor
loop, prova que as variáveis são declaradas fora do loop (length
ei
foram variáveis declaradas nofor
declaração de loop).
.locals init (
[0] int32 length,
[1] int32 i,
[2] bool CSEu entendo isso bem o suficiente porque eu entendo que cada vez que você fecha uma variável o compilador gera IL que o encapsula em uma nova classe feita especificamente para permitir que aquela variável seja capturada (essencialmente tornando-a um tipo de referência para que o valor esteja se referindo to não é destruído com o quadro de pilha do escopo atualmente em execução).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
Uma coisa que eu acho que o livro poderia ter feito melhor com relação a este assunto é realmente explicar o que o compilador está fazendo, porque toda essa "mágica" faz sentido se você entender que o compilador está envolvendo a variável fechada em uma nova classe.
Por favor corrija quaisquer equívocos ou mal entendidos que eu possa ter. Além disso, sinta-se à vontade para elaborar e / ou adicionar à minha explicação.