Código
while (Console.ReadKey(true).Key != ConsoleKey.Enter);
Lo que dice Daniel es cierto cuando el bucle es infinito por diseño: while(true), Así que ahí dependes del cuerpo del while para salir del bucle.
Si nos vamos a las entrañas de una aplicación en .net descompilándolo a IL tenemos:
Caso 1:
Código
while (Console.ReadKey(true).Key != ConsoleKey.Enter);
Código
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 29 (0x1d) .maxstack 2 .locals init ([0] valuetype [mscorlib]System.ConsoleKeyInfo CS$0$0000, [1] bool CS$4$0001) IL_0000: nop IL_0001: br.s IL_0003 IL_0003: ldc.i4.1 IL_0004: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool) IL_0009: stloc.0 IL_000a: ldloca.s CS$0$0000 IL_000c: call instance valuetype [mscorlib]System.ConsoleKey [mscorlib]System.ConsoleKeyInfo::get_Key() IL_0011: ldc.i4.s 13 IL_0013: ceq IL_0015: ldc.i4.0 IL_0016: ceq IL_0018: stloc.1 IL_0019: ldloc.1 IL_001a: brtrue.s IL_0003 IL_001c: ret } // end of method Program::Main
Caso 2:
Código
while (true) { if (Console.ReadKey(true).Key == ConsoleKey.Enter) { break; } }
Código
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 38 (0x26) .maxstack 2 .locals init ([0] valuetype [mscorlib]System.ConsoleKeyInfo CS$0$0000, [1] bool CS$4$0001) IL_0000: nop IL_0001: br.s IL_0021 IL_0003: nop IL_0004: ldc.i4.1 IL_0005: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool) IL_000a: stloc.0 IL_000b: ldloca.s CS$0$0000 IL_000d: call instance valuetype [mscorlib]System.ConsoleKey [mscorlib]System.ConsoleKeyInfo::get_Key() IL_0012: ldc.i4.s 13 IL_0014: ceq IL_0016: ldc.i4.0 IL_0017: ceq IL_0019: stloc.1 IL_001a: ldloc.1 IL_001b: brtrue.s IL_0020 IL_001d: nop IL_001e: br.s IL_0025 IL_0020: nop IL_0021: ldc.i4.1 IL_0022: stloc.1 IL_0023: br.s IL_0003 IL_0025: ret } // end of method Program::Main
- He resaltado la parte que es idéntica en ambos casos (los nop se ignoran porque no hacen nada (NOP = No Operation)), solo queda analizar que hace la parte no resaltada del segundo caso:
En la línea 21 del primer caso y 22 del segundo caso hacen el salto si la tecla presionada es Enter, es decir, eso debería de hacer que se salga del bucle
- En el primer caso salta a IL_0003 que es el inicio del bucle así que es muy sencillo entender que el bucle se volverá a ejecutar, si no salta, simplemente hace un RET para salir del bucle
- En el segundo caso salta a IL_0020 que es un NOP, este no hace nada, así que pasamos a la siguiente instrucción IL_0021, IL_0022 y IL_0023 donde ponen en la memoria (stack) 1, luego recuperan ese 1, y finalmente saltan incondicionalmente (si o si porque no comprueban nada) a IL_003 que es el inicio del bucle.
-- Si la tecla no fue Enter ejecutamos IL_001d que nuevamente es un NOP así que pasamos a IL_001e el cual es un salto incondicional a IL_0025, ahora bien, en IL_0025 está el RET para salir del bucle
Conclusión: El caso 1 es exactamente igual al caso dos, el caso dos es lo mismo salvo que hace otras otras inútiles, como NOP, poner valores en el stack, recuperarlas pero no hace absolutamente nada con ellas, salta a otros sitios que tampoco hacen nada, es decir, solo tiene código extra de relleno.
Por cierto he utilizado ildasm.exe para abrir el .exe generado y obtener su código IL que es al final lo que interpreta .NET, en mi caso lo tengo en C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe
Si todo lo anterior resulta un poquito complicado de entender se puede usar una herramienta más amigable para obtener el código IL que va a interpretar finalmente .NET, por ejemplo ILSpy: https://github.com/icsharpcode/ILSpy para mi siguiente ejemplo usaré una versión más viejita: https://github.com/icsharpcode/ILSpy/releases/download/v7.2.1/ILSpy_binaries_7.2.1.6856.zip que es compatible con .NET 4.7, las nuevas versiones requieren .NET 8
Los siguientes casos son como interpreta ILSpy el .exe generado, para estos casos obviare el IL puro porque ya lo expliqué antes, solo me centraré a la decompilación C# que obtiene ILSpy:
Caso 1 por ILSpy:
Código
// cprb.Program using System; private static void Main() { while (Console.ReadKey(intercept: true).Key != ConsoleKey.Enter) { } }
Caso 2 por ILSpy:
Código
// cprb.Program using System; private static void Main() { do { bool flag = true; } while (Console.ReadKey(intercept: true).Key != ConsoleKey.Enter); }
- Bueno, es obvio que el primer caso se parece más al ejemplo que entrega Microsoft con las {} al final, aunque el código original del caso uno no los tiene y en su lugar le puse ;
- El segundo caso, se interpreta como un do while, PERO el cuerpo de este no hace nada, es decir, crea una variable flag que pone a true pero no hace nada con ella, luego está la condicional del do while que es exáctamente igual al Caso 1.
---
Como extra también pongo las instrucciones finales C# interpretado y IL para el aporte que acaba de por Aincrad:
Original:
Código
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace cprb { class Program { static void Main() { Menu(); } private static bool Menu() { if (Console.ReadKey(true).Key == ConsoleKey.Enter) { return true; } else { return Menu(); } } } }
Interpretación C# por ILSpy:
Código
using System; internal class Program { private static void Main() { Menu(); } private static bool Menu() { if (Console.ReadKey(intercept: true).Key == ConsoleKey.Enter) { return true; } return Menu(); } }
Decompilación IL por ILSpy:
Código
.class private auto ansi beforefieldinit cprb.Program extends [mscorlib]System.Object { // Methods .method private hidebysig static void Main () cil managed { // Method begins at RVA 0x2050 // Header size: 1 // Code size: 8 (0x8) .maxstack 8 .entrypoint IL_0000: nop IL_0001: call bool cprb.Program::Menu() IL_0006: pop IL_0007: ret } // end of method Program::Main .method private hidebysig static bool Menu () cil managed { // Method begins at RVA 0x205c // Header size: 12 // Code size: 42 (0x2a) .maxstack 2 .locals init ( [0] bool, [1] valuetype [mscorlib]System.ConsoleKeyInfo, [2] bool ) IL_0000: nop IL_0001: ldc.i4.1 IL_0002: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool) IL_0007: stloc.1 IL_0008: ldloca.s 1 IL_000a: call instance valuetype [mscorlib]System.ConsoleKey [mscorlib]System.ConsoleKeyInfo::get_Key() IL_000f: ldc.i4.s 13 IL_0011: ceq IL_0013: ldc.i4.0 IL_0014: ceq IL_0016: stloc.2 IL_0017: ldloc.2 IL_0018: brtrue.s IL_001f IL_001a: nop IL_001b: ldc.i4.1 IL_001c: stloc.0 IL_001d: br.s IL_0028 IL_001f: nop IL_0020: call bool cprb.Program::Menu() IL_0025: stloc.0 IL_0026: br.s IL_0028 IL_0028: ldloc.0 IL_0029: ret } // end of method Program::Menu .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2092 // Header size: 1 // Code size: 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Program::.ctor } // end of class cprb.Program
---
Una vez Tachikomaia preguntó sobre la optimización de su código o porque no le funcionaban bien algunas instrucciones que requieren redondeo, en su caso el usa Flash 5 y yo comenté que podría usar JPEXS Free Flash Decompiler https://github.com/jindrapetrik/jpexs-decompiler para obtener el código final interpretado y el P-Code de sus .swf, esto es lo mismo que usar ILSpy para los .exe de .NET, con estos programas se puede ver que es lo que se ejecuta finalmente