En el cierre, ¿qué dispara una nueva instancia de la variable capturada?
Estoy leyendo la de Jon SkeetC # en profundidad.
En la página 156 tiene un ejemplo, Listado 5.13 "Captura de múltiples instancias de variables con múltiples 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]();
En la explicación después de esta lista, dice que "cada una de las instancias delegadas ha capturado una variable diferente en este caso".
Entiendo esto lo suficientemente bien porque entiendo que cada vez que cierras sobre una variable, el compilador genera un IL que lo encapsula en una nueva clase creada específicamente para permitir que esa variable sea capturada (esencialmente, es un tipo de referencia para que el valor al que se refiere) para no ser destruido con el marco de pila del ámbito de ejecución actual).
Pero luego él habla de lo que habría sucedido si hubiéramos capturadoindex
directamente en lugar de crear elcounter
variable - "todos los delegados habrían compartido la misma variable".
Esto no lo entiendo. No esindex
en el mismo ámbito quecounter
? ¿Por qué el compilador no creará también una nueva instancia deindex
para cada delegado?
Nota: Creo que lo descubrí mientras escribía esta pregunta, pero la dejaré aquí para la posteridad. Creo que la respuesta es queindex
Es en realidad en un ámbito diferente comocounter
. El índice se declara esencialmente "fuera" del bucle for ... es la misma variable cada vez.
Echando un vistazo a la IL generada para unafor
bucle, prueba que las variables están declaradas fuera del bucle (length
yi
Se declararon variables en lafor
declaración de bucle).
.locals init (
[0] int32 length,
[1] int32 i,
[2] bool CSEntiendo esto lo suficientemente bien porque entiendo que cada vez que cierras sobre una variable, el compilador genera un IL que lo encapsula en una nueva clase creada específicamente para permitir que esa variable sea capturada (esencialmente, es un tipo de referencia para que el valor al que se refiere) para no ser destruido con el marco de pila del ámbito de ejecución actual).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
Una cosa que creo que el libro podría haber hecho mejor con respecto a este tema es realmente explicar lo que está haciendo el compilador, porque toda esta "magia" tiene sentido si entiendes que el compilador está envolviendo la variable cerrada en una nueva clase.
Por favor, corrija cualquier malentendido o malentendido que pueda tener. Además, siéntase libre de elaborar y / o agregar a mi explicación.