Roberto Sánchez - Programador/Desarrollador .NET

Sentencias de control.

Como ya vimos en anteriores temas, la sentencia 'if' era la encargada de derivar la ejecución del programa en uno u otro sentido en función del resultado de la evaluación de una expresión condicional. Pero no sólo la sentencia 'if' puede realizar derivaciones en el curso de la ejecución, sino varias sentencias de control como son las siguientes: 'switch', 'for', 'while', 'do while', 'foreach', 'goto' y 'try'. Es absolutamente fundamental controlar a la perfección todas y cada una de estas sentencias de control. De lo contrario, nunca podrás realizar programas verdaderamente operativos y tampoco podrás entender el funcionamiento de código fuente ajeno a tí que caiga en tus manos. Las sentencias de control son, indudablemente, uno de los pilares fundamentales en que se sustenta no solamente C#, sino cualquier lenguaje de programación.

La sentencia de control 'if'.

Anteriormente ya vimos un poco por encima la sentencia 'if'. Recuerda que se trata de una sentencia que deriva la ejecución en uno u otro sentido en función de la evaluación de una expresión condicional. Por ejemplo:

int number = 1;
if (number < 0)
    System.Console.WriteLine("El número es negativo.");
else
    System.Console.WriteLine("El número es 0 o postivo.");

Evidentenmente, podemos construir sentencias 'if' anidadas, la ejecución de las cuales dependerá de la evaluación de la expresión condicional anterior:

int number = 1;
if (number < 0)
    System.Console.WriteLine("El número es negativo.");
else
{
    if (number > 0)
        System.Console.WriteLine("El número es positivo.");
    else
        System.Console.WriteLine("El número es 0.");
}

Recuerda que después de un 'if' o un 'else', si solamente deseamos que se ejecute una sentencia no será necesario el uso de un bloque de llaves, pero si deseamos que se ejecute un conjunto de líneas de sentencias entonces será necesario encerrar entre un bloque de llaves ese conjunto de sentencias que deben ejecutarse según la evaluación de la expresión condicional.

Hasta ahora, en los ejemplos que hemos visto con la sentencia 'if', las expresiones condicionales hacían referencia a una sola expresión. Sin embargo, pueden ser más de una, como ilustra el ejemplo siguiente:

int number1 = 1;
int number2 = 2;
if ((number1 > number2) && (number2 - number1 == 1))
    System.Console.WriteLine("number1 es mayor que number2 0 su resta es igual a 1."

Si analizas el código anterior te darás cuenta de que el resultado de la expresión condicional es 'false', por lo que la condición no se cumple y la siguiente línea de código después del 'if' no se ejecuta.

Sentencia de control 'switch'.

La sentencia 'switch' evalúa su expresión condicional y, según el resultado de ella, elige entre varias opciones que tienen posibles resultados. Es una sentencia de control que, en ocasiones, puede reemplazar a varias sentencias 'if'. Veamos un ejemplo:

string name = "Roberto";
switch (name)
{
    case "Zipi":
        System.Console.WriteLine("Hola Zipi");
        break;
    case "Roberto":
        System.Console.WriteLine("Hola Roberto");
        break;
    default:
        System.Console.WriteLine("Hola persona desconocida.");
        break;
}

La sentencia de control 'switch' toma el valor de la cadena de texto de la variable 'name' y la compara con las posibilidades que tiene implementadas en cada bloque de código encabezado por 'case'. En este caso, la ejecución del código se decantará irremisiblemente por la segunda opción, que es aquella en que el bloque 'case' hace referencia a que la cadena de texto debe ser 'Roberto', y por lo tanto ejecuta ese bloque de código saludando de manera específica a Roberto. Pero imaginemos por un momento, que el valor de la variable 'name' es, por ejemplo, "Zape"; en ese caso, la sentencia 'switch' evaluará si hay coincidencia con algunas de las posibilidades de los bloques de código encabezados por cada 'case' y, como no es el caso, finalmente se decantará por defecto por el bloque de código encabezado por la instrucción 'default' y el saludo será 'Hola persona desconocida'. Así pues, 'default' cubre todas aquellas posibilidades que no se evaluaron, y es un recurso fundamental al que no debemos renunciar.

La pregunta obligada es: ¿qué pasaría si renunciáramos al bloque 'default' y no se cumpliera ninguna coincidencia en el siguiente código? Pues al no cumplirse ninguna coincidencia contemplada en la sentencia 'switch', la ejecución del código continuaría a continuación de este de forma natural:

string name = "Roberto";
switch (name)
{
    case "Zipi":
        System.Console.WriteLine("Hola Zipi");
        break;
    case "Roberto":
        System.Console.WriteLine("Hola Roberto");
        break;
}

Hay que tener en cuenta que no siempre hay que incluir una instrucción 'break' al final de cada bloque de código. Por ejemplo:

int number = 3;
switch (number)
{
    case 0:
    case 1:
    case 2:
    case 3:
        System.Console.WriteLine("El número es 0, 1, 2 ó 3.");
        break;
    case 4:
        System.Console.WriteLine("El número es 4.");
        break;
    default:
        System.Console.WriteLine("El número es negativo o mayor que cuatro.");
        break;
}

Sentencia de control 'for'.

La función de la sentencia de control 'for' es la de realizar repeticiones de una sentencia o conjunto de sentencias que estén encerradas dentro de su bloque de llaves. En este caso, no se trata solamente de la evaluación de un expresión condicional como antes, sino de simplemente repetir tantas veces como quede establecido en la condición de la sentencia 'for' mientras no se cumpla la expresión condicional.

int counter;
for (counter = 1; counter <= 10; counter++)
{
    System.Console.WriteLine("El valor del contador es: " + counter);
    if (counter != 10)
        System.Console.WriteLine("El siguiente valor del contador será: {0}", counter + 1);
}

Analiza detenidamente el código anterior. Se establece el valor de la variable 'counter' en 1. Se evalúa si el valor de 'counter' es meno o igual que 10. En caso afirmativo se ejecuta el bloque de código que encierra la sentencia 'for' y, al acabarla, el valor de la variable 'counter' se incrementa en 1 para volver a evaluarse de nuevo desde el principio. A estas alturas del curso ya deberías saber interpretar correctamente todo lo que conlleva la ejecución de este bloque de código.

Evidentemente, dentro del bloque de código que ejecuta un bucle 'for' puede haber otras sentencias de control cualesquira e, incluso, otras sentencias de control 'for', tantas como queramos o sean necesarias, anidadas o no anidadas, tal y como vimos en el caso de la sentencia 'if'.

Además, puede darse el caso que en una sentencia 'for' queramos ampliar la funcionalidad de la expresión condicional, como muestra el siguiente código que dejo a tus conocimientos adquiridos hasta ahora para evaluar su resultado:

int i, j;
bool done;
for (i = 0, j = 100; !done; i++, j++)
{
    if (i * i >= j)
        done = true;
    System.Console.WriteLine("i, j: " + i + ", " + j);
}

Sentencia de control 'while'.

La función de la sentencia de control 'while' es la de repetir la ejecución de una sentencia o un bloque de sentencias encerradas entre llaves todas las veces que sean necesarias mientras se cumpla la expresión condicional expresada en la sentencia.

int counter = 0;
while (counter != 9)
{
    System.Console.WriteLine("El valor del contador es: {0}", counter);
    counter++;
}

El valor del contador se inicia a 0 y se pasa a evaluar la expresión condicional. ¿Es distinto de 0? En este caso sí, por lo oque se ejecutan las sentencias encerradas en el bloque de llaves, incluida la sentencia en la que se suma una unidad al valor del contador, para después volver a evaluar la expresión condicional. Se irán sucediendo repetidas pasadas por el buble 'while' hasta llegar un momento en el que el valor del contador no es distinto de 9 (puesto que habrá alcanzado el valor 9). En ese momento la expresión condicional se cumple y, por lo tanto, ya no se ejecuta más el buble 'while', pasando la ejecución a la siguiente sentencia después del bucle.

Al igual que ocurría con la sentencia 'for', la sentencia 'while' permite una combinación de evaluaciones dentro de la expresión condicional. Por ejemplo:

bool done = false;
int counter = 0;
while (counter != 9 || done == true)
{
    System.Console.WriteLine("El valor del contador es: {0}", counter);
    counter++;
}

Analiza detenidamente la expresión condicional. La pregunta es: ¿se terminará en algún momento la ejecución del buble 'while', teniendo en cuenta que la variable 'done' siempre tendrá como valor 'false'? La respuesta es sí, porque la expresión condicional dice: si el valor de contador es distinto de 9 o 'done' es igual a 'true', es decir, que independientemente del valor de 'done', cuando el valor del contador sea 9, él resultado de la evaluación de la expresión condicional será 'true'. Como consecuencia de ello, el bucle se ejecutará 10 veces.

Sentencia de control 'do while'.

Visto el funcionamiento de la sentencia 'while', la sentencia 'do while' entraña poca dificultad. En este caso, la expresión de evaluación no se encuentra al pricipio, sino al final del bucle, por lo que éste siempre se ejecutará al menos una vez.

bool done = false;
int counter = 0;
do
{
    System.Console.WriteLine("El valor del contador es: {0}", counter);
    counter++;
} while (counter != 9 || done == true)

Utilizando las mismas variables que en ejemplo anterior, ahora vemos que el bucle se ejecuta siempre al menos una vez, puesto que la expresión condicional se encuentra después de éste. Al finalizar esta primera ejecución, y si la expresión condicional no se cumple, volverá a ejecutarse, y así sucesivamente hasta que, en este caso, el valor del contador sea igual a 9, en cuyo momento la ejecución del programa abandonará el buble 'do while' y pasará a la siguiente sentencia. Como consecuencia de ello, el buble también se ejecutará 10 veces.

Sentencia de control 'foreach'.

El funcionamiento de la sentencia de control 'foreach' puede representar una dificultad en su apredizaje y entendimiento algo por encima de lo que hemos estudiado hasta ahora con las otras sentencias de control. Su funcionamiento podríamos definirlo como un buble que realiza un ciclo a través de los elementos de una colección, entendiendo por ésta un conjunto de objetos. Es posible forzar la salida inmediata de un bucle 'foreach', omitiendo el código restante en el cuerpo del buble y la prueba condicional del mismo mediante la instrucción 'break'.

El funcionamiento de la sentencia de control 'foreach' está íntimamente ligada con las matrices y, dado que aún no hemos estudiado éstas, es posible que tengas alguna duda que, sin duda, despejarás al estudiar las matrices. No te preocupes. Veamos un ejemplo:

int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };
foreach (int i in numbers)
    System.Console.WriteLine("{0} ", i);

Imagino que has tenido dificultad en entender la inicialización de la variable 'numbers'. Se trata de una matriz y, de momento, lo que debes entender es que es una variable que está formada por una colección, en este caso una colección de números de tipo 'int'. Sabiendo esto, el buble 'foreach' recorre secuencialmente la matriz desde el índice inferior hasta el índice superior. No habiendo ninguna instrucción 'break' en el buble, la secuencia se repetirá hasta haber examinado todos y cada uno de los elementos de la matriz.

El resultado de la ejecución de este buble 'foreach' dará el siguiente resultado en la salida de la consola: '4 5 6 1 2 3 -2 -1 0'. Pero vamos a complicarlo un poco agregando una condición de salida del bucle:

int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };
foreach (int i in numbers)
{
    System.Console.WriteLine("{0} ", i);
    if (numbers[i] == 3)
        break;
}

No habrás tenido dificultad para comprender que ahora el 'if' comprueba que el valor de la variable 'numbers' sea igual a 3, y que lo hace en todas las pasadas tomando el valor de 'i' para establecer el índice de la matriz. Es decir, en la primera pasada 'if (numbers[0] == 3)' ('numbers[0]' es igual a 4), en la segunda pasada 'if (numbers[1] == 3)' ('numbers[1]' es igual a 5), en la tercera pasada 'if (numbers[2] == 3)' ('numbers[2]' es igual a 6), y así sucesivamente hasta llegar a la condición 'if (numbers[5] == 3)' ('numbers[5]' es igual a 3), en la que la expresión condicional se cumple y, por lo tanto, se ejecuta la instrucción 'break' para salir del buble. Ahora el resultado de salida en la consola sería el siguiente: '4 5 6 1 2 3'.

La pregunta que probablemente te hayas hecho es: ¿para wué necesito 'foreach' si lo que consigo con él lo puedo hacer con un 'for'? La respuesta no te la voy a dar ahora, pero ten por seguro que este ejemplo sencillo no muestra la capacidad de 'foreach' para, por ejemplo, realizar búsquedas en matrices y colecciones verdaderamente extensas que, sin el uso de 'foreach', nos costaría muchas líneas de código fuente. Con el tiempo encontrarás multitud de ocasiones en que un 'foreach' realizará una tarea a la perfección y te ahorrarás esfuerzo innecesario.

Sentencia de control 'goto'.

La sentencia de control 'goto' de C# posee una mala prensa injustificada en la mayoría de los manuales de programación e incluso en las preguntas y respuestas de foros de comunidades de programadores. Es como el tópico de "el café es malo"; sí, pero como todo, si lo tomas en exceso. Pues pasa algo parecido con la sentencia 'goto'. Usada de manera inteligente y en muy contadas ocasiones, puede sernos de gran utilidad para controlar el flujo de ejecución en un programa y, sin él, la cosa se nos complicaría mucho.

Lo dicho hasta ahora lo afirmo aquí y en cualquier ámbito. Pero también tengo claro un asunto, y tú lo debes tener también claro: lo que se puede hacer con un 'goto' se puede hacer de otras maneras. En ti queda la opción de su uso, dónde, cuándo y para qué, siempre después de evaluar otras posibilidades para su uso. Veamos un ejemplo del uso de 'goto' en una implementación con la sentencia de control 'switch':

public class GotoTest
{
    public static void Main()
    {
        System.Console.WriteLine("Tamaños de cafés: 1=pequeño, 2=medio, 3=largo.");
        System.Console.WriteLine("Introduce tu selección:");
        string selection = System.Console.ReadLine();
        int numericSelection = int.Parse(selection);
        int cost = 0;
        switch (numericSelection)
        {
            case 1:
                cost += 25;
                break;
            case 2:
                cost += 25;
                goto case 1;
            case 3:
                cost += 50;
                goto case 1;
            default:
                System.Console.WriteLine("Selección no válida.");
                break;
        }
        if (cost != 0)
            System.Console.WriteLine("Por favor, introduce {0} céntimos.", cost);
        System.Console.ReadKey();
    }
}

El programa pide una selección (1, 2 ó 3) según la opción deseada y a continuación, convierte en 'int' la cadena de texto introducida por el teclado. Una vez realizado esto, la sentencia 'switch' evalúa el valor numérico de la selección a través de la variable 'numericSelection'. En el caso de la opción 1, simplemente establece el costo en 25 céntimos y la ejecución se sale del boble 'switch' mediante la sentencia 'break'; en el caso de la opción 2, se establece el costo en 25 céntimos y mediante el 'goto' se salta a la opción 1 para sumar otros 25 céntimos y salir del bloque 'switch' mediante la sentencia 'break'; y en el caso de la opción 3, se establece el costo en 50 céntimos y mediante el 'goto' se salta también a la opción 1 para sumar otros 25 céntimos y salir del bloque 'switch' mediante la sentencia 'break'. Resumiento, en el caso 1 el costo será de 25 céntimos, en el caso 2 de 50 céntimos y en el caso 3 de 75 céntimos.

Tal como te dije antes, habrás podido comprobar que sería posible realizar la misma tarea con otro tipo de instrucciones sin hacer uso de la sentencia 'goto'. Siempre es posible realizar la misma tarea con otras sentencias e instrucciones sin tener que hacer uso del 'goto'. En tus manos quedará la opción de su uso si crees que te facilitará el trabajo y proporcionará ahorro en líneas de código fuente.

Pero además hay otro tipo de implementación de la sentencia de control 'goto'. Hemos visto su uso de direccionamiento a otro supuesto dentro de un bloque 'switch', y ahora veremos un direccionamiento a un identificador, también llamado etiqueta. Veamos:

int counter = 0;
AGAIN:
counter++;
if (counter < 100)
    goto AGAIN;
else
    goto FINISH;
FINISH:

Se inicializa la variable 'counter' a 0 y se pasa la siguiente línea, en la cual se encuentra la etiqueta 'AGAIN', pasando sobre ella hacia la siguiente línea, en la cual se suma una unidad al valor de la variable 'counter'. Ahora el valor de la variable 'counter' es 1, y en el 'if' se pregunta si su valor es menor que 100. Como, efectivamente, su valor es menor de 100, la sentencia 'goto' ordena el salto a la etiqueta 'AGAIN' y, por lo tanto, a la siguiente línea, en la cual se vuelve a sumar una unidad al valor de la variable 'counter'. Cuando este valor sea 100, entonces no se cumplirá la expresión condicional y se ejecutará la línea que sigue a continuación de la sentencia 'else', en la cual nos encontramos un 'goto' que nos direcciona a la etiqueta 'FINISH' y nos sacará del bucle en el cual hemos estado aumentando el valor de la variable 'counter' hasta 100.

Sentencia de control 'try'.

La sentencia de control 'try' consta de un bloque 'try' seguido de una o más cláusulas 'catch' que especifican controladores para diferentes excepciones. Cuando se produce una excepción, la ejecución salta a la cláusula 'catch' que controla esta excepción. Si no existe ningún bloque 'catch', el compilador mostrará al usuario un mensaje de excepción no controlada y detendrá la ejecución del programa, situación que se debe evitar. El bloque 'try' contiene el código protegido que puede producir la excepción y se ejecuta hasta que se produce una excepción o hasta que se completa correctamente. Por ejemplo, el intento siguiente de convertir a 'int' un objeto 'null' produce una excepción:

object obj = null;
try
{
    int i = (int)obj;
    System.Console.WriteLine("Conversión realizada con éxito.");
}
catch
{
    System.Console.WriteLine("Error: conversión no válida.");
}

Como ves, se intenta convertir un objeto cuyo valor es 'null' a entero. Esta conversión es imposible de realizar, por lo que directamente se abandona el bloque 'try' y se ejecuta el contenido del bloque 'catch', en el que se informa al usuario de que la conversión no es válida. Evidentemente, la línea en la que se informa al usuario de que la conversión de ha realizado con éxito nunca se ejecutará, pues antes de ello se ha producido la interrupción y se ha saltado al bloque 'catch'.

El conjunto 'try-catch' es especialmente útil en las fases de depuración y prueba de un programa, ya que nos permite comprobar que un bloque de intrucciones se va a realizar con éxito en su totalidad. Pero, además, podemos añadir un parámetro a la cláusula 'catch' que nos permitirá conocer exactamente qué tipo de interrupción se produce.

object obj = null;
try
{
    int i = (int)obj;
    System.Console.WriteLine("Conversión realizada con éxito.");
}
catch (System.Exception ex)
{
    System.Console.WriteLine(ex.Message);
}

Como ves, hemos añadido un parámetro llamado 'ex' de la clase 'Exception', cuya propiedad 'Message' hemos volcado a la salida de la consola. Esta propiedad será el nombre exacto que el compilador de a la excepción producida, lo cual nos indicará exactamente qué tipo de error se está produciendo.

object obj = null;
try
{
    int i = (int)obj;
    System.Console.WriteLine("Conversión realizada con éxito.");
}
catch (System.ArgumentNullException ex1)
{
    System.Console.WriteLine(ex1.Message);
}
catch (System.Exception ex2)
{
    System.Console.WriteLine(ex2.Message);
}

Ahora hemos añadido dos cláusulas 'catch' para "acorralar" más la la excepción producida. La primera cláusula 'catch' se ejecutará sólo si la excepción producida es del tipo 'ArgumentNullException' (lo cual se cumplirá en este caso). En caso negativo, se pasará al siguiente bloque 'catch', en el cual el compilador mostrará de qué otro tipo de interrupción se ha tratado.

Y, por último, veamos una posibilidad más de 'try': la cláusula 'finally'. Esta cláusula se ejecutará siempre, pero solamente lo hará al final de la evaluación de todos los bloques 'catch'. Por ejemplo, y tomando el ejemplo anterior:

object obj = null;
try
{
    int i = (int)obj;
    System.Console.WriteLine("Conversión realizada con éxito.");
}
catch (System.ArgumentNullException ex1)
{
    System.Console.WriteLine(ex1.Message);
}
catch (System.Exception ex2)
{
    System.Console.WriteLine(ex2.Message);
}
finally
{
    System.Console.WriteLine("Todas las cláusulas 'catch' evaluadas.");
}