Cuando empezamos a programar, una de las cosas que suelen quedar bastante claras (o no) es la diferencia entre i++, ++i e i+=1, mientras que la primera lee y luego asigna, la segunda asigna y luego lee, y la tercera lee y asigna. Personalmente no estaba del todo convencido, así que decidí verlo por mí mismo utilizando el compilador de C#, y comprobar cual era el código IL generado. Las funciones utilizadas son muy simples, y el código se ha compilado en modo debug.
¿Por qué el modo debug? Porque el modo release hace una serie de optimizaciones que dan como resultado exactamente el mismo código IL para un comportamiento sencillo. Veamos la primera función, con su respectivo código IL:
void APlusPlus(int A) { int B = A++; }
El código IL es el siguiente:
.method private hidebysig instance void APlusPlus(int32 A) cil managed { // Code size 9 (0x9) .maxstack 3 .locals init ([0] int32 B) IL_0000: nop IL_0001: ldarg.1 IL_0002: dup IL_0003: ldc.i4.1 IL_0004: add IL_0005: starg.s A IL_0007: stloc.0 IL_0008: ret } // end of method Sample::APlusPlus
En este caso el funcionamiento es el siguiente:
- Se carga el valor de A en la pila
- Se duplica el valor de A.
- Se suma 1 al valor de A
- Se almacena el nuevo valor de A.
- Se almacena el antiguo valor de A en B (A = B + 1).
Veamos el segundo candidato, en este caso la suma se realiza antes de la lectura.
void PlusPlusA(int A) { int B = ++A; }
El código IL se muestra a continuación:
.method private hidebysig instance void PlusPlusA(int32 A) cil managed { // Code size 9 (0x9) .maxstack 2 .locals init ([0] int32 B) IL_0000: nop IL_0001: ldarg.1 IL_0002: ldc.i4.1 IL_0003: add IL_0004: dup IL_0005: starg.s A IL_0007: stloc.0 IL_0008: ret } // end of method Sample::PlusPlusA
En este caso el funcionamiento es el siguiente:
- Se carga el valor de A en la pila
- Se suma 1 al valor de A
- Se duplica el valor del elemento almacenado en la pila.
- Se almacena el nuevo valor en A.
- Se almacena el nuevo valor en B (A = B).
Finalmente, veremos qué pasa con la construcción A+=1;
void APlusOne(int A) { int B = A += 1; }
El código IL es el siguiente:
.method private hidebysig instance void APlusOne(int32 A) cil managed { // Code size 9 (0x9) .maxstack 2 .locals init ([0] int32 B) IL_0000: nop IL_0001: ldarg.1 IL_0002: ldc.i4.1 IL_0003: add IL_0004: dup IL_0005: starg.s A IL_0007: stloc.0 IL_0008: ret } // end of method Sample::APlusOne
Si comparamos con el código anterior veremos que el resultado es exactamente igual, sin ningún cambio.
Conclusiones
Estas pequeñas pruebas nos permiten ver cómo funciona internamente el código IL, así como comprobar cómo se manipulan los objetos en operaciones sencillas. Si ejecutamos estas mismas operaciones en modo release cambia por completo, ya que el compilador elimina código no utilizado, así como valores nop que se insertan para facilitar la depuración.
Más info de ildasm en MSDN
Muy interesante. Mola de vez en cuando ver post diferentes!
Sigue así!