Eres libre de usar la forma que veas por conveniente, pero a simple vista para tu caso sencillo donde quieres que no se salga del programa hasta que se presione Enter debería bastar con:
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:while (Console.ReadKey(true).Key != ConsoleKey.Enter);
.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:while (true) {
if (Console.ReadKey(true).Key == ConsoleKey.Enter) { break; }
}
.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:
// cprb.Program
using System;
private static void Main()
{
while (Console.ReadKey(intercept: true).Key != ConsoleKey.Enter)
{
}
}
Caso 2 por ILSpy:
// 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:
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:
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:
.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