Llevamos ya varias entregas de la serie C# bizarro, donde, como sabéis, ponemos a prueba nuestros conocimientos de C# llevando al extremo el uso de algunas de sus características, lo que de paso nos ayuda a conocer mejor aún el lenguaje.
Hoy vamos a proponer un nuevo reto, mostrando un comportamiento que quizás muchos conozcáis, pero seguro que otros nunca habéis tenido ocasión de verlo. A ver qué tal se os da ;)
Observad atentamente esta sencilla aplicación de consola:
int i = 1;
int accum = 0;
while (i <= 5)
{
accum += i;
Debug.WriteLine($"Adding {i++}, Accumulated = {accum}");
}
Console.WriteLine($"Total: {accum}");
A la vista del código, las preguntas son dos: ¿Qué resultado obtenemos al ejecutar la aplicación? ¿Será siempre así?
Obviamente, tiene algo de truco, pero si lo pensáis un poco seguro que podéis responder a las preguntas. Y si no, pulsad aquí para ver la solución; 👇👇
En principio, lo lógico es pensar que deberíamos ver en el canal de depuración (la ventana Output>Debug en Visual Studio) el valor actual y el total acumulado en cada iteración, lo que nos permite ir siguiendo en detalle el funcionamiento interno:
Adding 1, Accumulated = 1
Adding 2, Accumulated = 3
Adding 3, Accumulated = 6
Adding 4, Accumulated = 10
Adding 5, Accumulated = 15
Finalmente, se mostrará por consola el valor definitivo:
Total: 15
Esto será justamente lo que obtengamos si copiamos el código en Visual Studio y lo ejecutamos pulsando F5 o el botón correspondiente del IDE. Hasta ahí, perfecto ;)
Sin embargo, este código tiene una trampa oculta. Si desde Visual Studio cambiamos el modo de compilación a "Release" y lo lanzamos, o bien si lanzamos directamente el ejecutable que encontraremos en la carpeta bin/Release/net8.0
(o la versión de .NET que uses, da igual), veremos que la aplicación no se detiene nunca (?!)
El motivo de este extraño comportamiento lo explicamos hace ya bastantes años por aquí, en el post métodos condicionales en C#.
Estos métodos, presentes desde las primeras versiones de .NET (pre-Core), se decoran con el atributo [Conditional]
para hacer que éstos y todas las referencias a los mismos sean eliminadas del ensamblado resultante de la compilación si no existe una constante de compilación determinada.
De hecho, si acudimos al código fuente de la clase estática Debug
, veremos que su método WriteLine()
está definido de la siguiente manera:
public static partial class Debug
{
...
[Conditional("DEBUG")]
public static void WriteLine(string? message)
=> s_provider.WriteLine(message);
}
Cuando compilamos en modo depuración, la constante DEBUG
estará definida, por lo que este método podrá ser invocado con normalidad y todo funcionará bien. Sin embargo, si compilamos en Release o cualquier otra configuración que no incluya la constante, este método desaparecerá del ensamblado, junto con las referencias que lo utilicen.
Es decir, si usamos el modo Release, el código que hemos escrito antes quedará tras la compilación como el siguiente:
int i = 1;
int accum = 0;
while (i <= 5)
{
accum += i;
// La siguiente llamada será eliminada en compilación:
// Debug.WriteLine($"Adding {i++}, Accumulated = {accum}");
}
Console.WriteLine($"Total: {accum}");
Fijaos que, al eliminar la llamada, con ella desaparecerá también la expresión de autoincremento del índice del bucle i++
, por lo que nunca se alcanzará la condición de salida y quedará iterando de forma indefinida.
Bonito, ¿eh? ;)
Publicado en Variable not found.
Aún no hay comentarios, ¡sé el primero!
Enviar un nuevo comentario