Estructuras de control

    Las instrucciones condicionales son instrucciones que permiten ejecutar bloques de instrucciones sólo si se da una determinada condición. En los siguientes subapartados de este epígrafe se describen cuáles son las instrucciones condicionales disponibles en C#

Instrucción if

La instrucción if permite ejecutar ciertas instrucciones sólo si de da una determinada condición. Su sintaxis de uso es la sintaxis:


 if (<condición>){
   <instruccionesIf>}
 else{
    <instruccionesElse>}

    El significado de esta instrucción es el siguiente: se evalúa la expresión <condición>, que ha de devolver un valor lógico. Si es cierta (devuelve true) se ejecutan las <instruccionesIf>, y si es falsa (false) se ejecutan las <instruccionesElse> La rama else es opcional, y si se omite y la condición es falsa se seguiría ejecutando a partir de la instrucción siguiente al if. En realidad, tanto <instruccionesIf> como <instruccionesElse> pueden ser una única instrucción o un bloque de instrucciones.

Un ejemplo de aplicación de esta instrucción es esta variante del HolaMundo:


using System;
class HolaMundoIf
{
    public static void Main(String[] args)
    {          
         if (args.Length > 0){
           Console.WriteLine("Hola {0}!", args[0]);}
         else{
Console.WriteLine("Hola mundo!");}
    }
}

    Si ejecutamos este programa sin ningún argumento veremos que el mensaje que se muestra es ¡Hola Mundo!, mientras que si lo ejecutamos con algún argumento se mostrará un mensaje de bienvenida personalizado con el primer argumento indicado.

Instrucción switch

    La instrucción switch permite ejecutar unos u otros bloques de instrucciones según el valor de una cierta expresión. Su estructura es:


  switch (<expresión>)
  {
    case <valor1>:  <bloque1>
     <siguienteAcción>
    case <valor2>:  <bloque2>
     <siguienteAcción>
    ...
    default:  <bloqueDefault>
     <siguienteAcción>
  }

    El significado de esta instrucción es el siguiente: se evalúa . Si su valor es se ejecuta el , si es se ejecuta , y así para el resto de valores especificados. Si no es igual a ninguno de esos valores y se incluye la rama default, se ejecuta el ; pero si no se incluye se pasa directamente a ejecutar la instrucción siguiente al switch.

    Los valores indicados en cada rama del switch han de ser expresiones constantes que produzcan valores de algún tipo básico entero, de una enumeración, de tipo char o de tipo string. Además, no puede haber más de una rama con el mismo valor.

    En realidad, aunque todas las ramas de un switch son opcionales siempre se ha de incluir al menos una. Además, la rama default no tiene porqué aparecer la última si se usa, aunque es recomendable que lo haga para facilitar la legibilidad del código.

    El elemento marcado como <siguienteAcción> colocado tras cada bloque de instrucciones indica qué es lo que ha de hacerse tras ejecutar las instrucciones del bloque que lo preceden. Puede ser uno de estos tres tipos de instrucciones:


goto case <valori>;
goto default;
break;
 
    Si es un goto case indica que se ha de seguir ejecutando el bloque de instrucciones asociado en el switch a la rama del <valori> indicado, si es un goto default indica que se ha de seguir ejecutando el bloque de instrucciones de la rama default, y  si es un break indica que se ha de seguir ejecutando la instrucción siguiente al switch.

    El siguiente ejemplo muestra cómo se utiliza switch:


using System;
class HolaMundoSwitch
{
   public static void Main(String[] args)
  {
      if (args.Length > 0)
        switch(args[0])
        {
         case "José":
Console.WriteLine("Hola José. Buenos días");
              break;
         case "Paco":
Console.WriteLine("Hola Paco. Me alegro de verte");
              break;
         default:
Console.WriteLine("Hola {0}", args[0]);
              break;
       }
     else
        Console.WriteLine("Hola Mundo");
  }
}

    Este programa reconoce ciertos nombres de personas que se le pueden pasar como argumentos al lanzarlo y les saluda de forma especial. La rama default se incluye para dar un saludo por defecto a las personas no reconocidas.

    Para los programadores habituados a lenguajes como C++ es importante resaltarles el hecho de que, a diferencia de dichos lenguajes, C# obliga a incluir una sentencia break o una sentencia goto case al final de cada rama del switch para evitar errores comunes y difíciles de detectar causados por olvidar incluir break; al final de alguno de estos bloques y ello provocar que tras ejecutarse ese bloque se ejecute también el siguiente.

Instrucciones iterativas

    Las instrucciones iterativas son instrucciones que permiten ejecutar repetidas veces una instrucción o un bloque de instrucciones mientras se cumpla una condición. Es decir, permiten definir bucles donde ciertas instrucciones se ejecuten varias veces. A continuación se describen cuáles son las instrucciones de este tipo incluidas en C#.

Instrucción while

    La instrucción while permite ejecutar un bloque de instrucciones mientras se de una cierta instrucción. Su sintaxis de uso es:


while
(<condición>)
{
  <instrucciones>
}

    Su significado es el siguiente: Se evalúa la <condición> indicada, que ha de producir un valor lógico. Si es cierta (valor lógico true) se ejecutan las <instrucciones> y se repite el proceso de evaluación de <condición> y ejecución de <instrucciones> hasta que deje de serlo. Cuando sea falsa (false) se pasará a ejecutar la instrucción siguiente al while. En realidad <instrucciones> puede ser una única instrucción o un bloque de instrucciones.

    Un ejemplo cómo utilizar esta instrucción es el siguiente:


using System;
class HolaMundoWhile
{
    public static void Main(String[] args)
    {
       int actual = 0;
       if (args.Length > 0)
           while (actual < args.Length)
           {
            Console.WriteLine("¡Hola {0}!", args[actual]);
            actual  = actual + 1;
           }
       else
           Console.WriteLine("¡Hola mundo!");
    }
}

    En este caso, si se indica más de un argumento al llamar al programa se mostrará por pantalla un mensaje de saludo para cada uno de ellos. Para ello se usa una variable actual que almacena cuál es el número de argumento a mostrar en cada ejecución del while. Para mantenerla siempre actualizada lo que se hace es aumentar en una unidad su valor tras cada ejecución de las <instrucciones> del bucle.

    Por otro lado, dentro de las <instrucciones> de un while pueden utilizarse las siguientes dos instrucciones especiales:

  • break;: Indica que se ha de abortar la ejecución del bucle y continuarse  ejecutando por la instrucción siguiente al while.

  • continue;: Indica que se ha de abortar la ejecución de las <instrucciones> y reevaluarse la <condición> del bucle, volviéndose a ejecutar las <instrucciones> si es cierta o pasándose a ejecutar la instrucción siguiente al while si es falsa.

Instrucción do...while

    La instrucción do...while es una variante del while que se usa así:


do {
    <instrucciones>
} while(<condición>);
       

    La única diferencia del significado de do...while respecto al de while es que en vez de evaluar primero la condición y ejecutar <instrucciones> sólo si es cierta, do...while primero ejecuta las <instrucciones> y luego mira la <condición> para ver si se ha de repetir la ejecución de las mismas. Por lo demás ambas instrucciones son iguales, e incluso también puede incluirse break; y continue; entre las <instrucciones> del do...while.

    do ... while está especialmente destinado para los casos en los que haya que ejecutar las <instrucciones> al menos una vez aún cuando la condición sea falsa desde el principio, como ocurre en el siguiente ejemplo:

 
using System;
class HolaMundoDoWhile
{
  public static void Main()
  {
      String leído;
      do
      {
         Console.WriteLine("Clave: ");
         leído = Console.ReadLine();
      }
      while (leído != "José");
      Console.WriteLine("Hola José");
  }
}

    Este programa pregunta al usuario una clave y mientras no introduzca la correcta (José) no continuará ejecutándose. Una vez que introducida correctamente dará un mensaje de bienvenida al usuario.

Instrucción for

    La instrucción for es una variante de while que permite reducir el código necesario para escribir los tipos de bucles más comúnmente usados en programación. Su sintaxis es:


for (<inicialización>; <condición>; <modificación>){
 <instrucciones>
 }

    El significado de esta instrucción es el siguiente: se ejecutan las instrucciones de <inicialización>, que suelen usarse para definir e inicializar variables que luego se usarán en <instrucciones>. Luego se evalúa <condición>, y si es falsa se continúa ejecutando por la instrucción siguiente al for; mientras que si es cierta se ejecutan las <instrucciones> indicadas, luego se ejecutan las instrucciones de <modificación> -que como su nombre indica suelen usarse para modificar los valores de variables que se usen en <instrucciones>- y luego se reevalúa <condición> repitiéndose el proceso hasta que ésta última deje de ser cierta.

    En <inicialización> puede en realidad incluirse cualquier número de instrucciones que no tienen porqué ser relativas a inicializar variables o modificarlas, aunque lo anterior sea su uso más habitual. En caso de ser varias se han de separar mediante comas (,), ya que el carácter de punto y coma (;) habitualmente usado para estos menesteres se  usa en el for para separar los bloques de <inicialización>, <condición> y <modificación> Además, la instrucción nula no se puede usar en este caso y tampoco pueden combinarse definiciones de variables con instrucciones de otros tipos.

    Con <modificación> pasa algo similar, ya que puede incluirse código que nada tenga que ver con modificaciones pero en este caso no se pueden incluir definiciones de variables.

    Como en el resto de instrucciones hasta ahora vistas, en <instrucciones> puede ser tanto una única instrucción como un bloque de instrucciones. Además, las variables que se definan en <inicialización> serán visibles sólo dentro de esas <instrucciones>.

    La siguiente clase es equivalente a la clase HolaMundoWhile ya vista solo que hace uso del for para compactar más su código:


using System;
class HolaMundoFor
{
    public static void Main(String[] args)
    {
       if (args.Length > 0)
         for (int actual = 0; actual < args.Length; actual++) {
             Console.WriteLine("¡Hola {0}!", args[actual]);
         }
       else
         Console.WriteLine("¡Hola mundo!");
    }
}

    Al igual que con while, dentro de las <instrucciones> del for también pueden incluirse instrucciones continue; y break; que puedan alterar el funcionamiento normal del bucle.

Instrucción foreach

    La instrucción foreach es una variante del for pensada especialmente para compactar la escritura de códigos donde se realice algún tratamiento a todos los elementos de una colección, que suele un uso muy habitual de for en los lenguajes de programación que lo incluyen. La sintaxis que se sigue a la hora de escribir esta instrucción foreach es:


foreach (<tipoElemento> <elemento> in <colección>) {
   <instrucciones>
}

    El significado de esta instrucción es muy sencillo: se ejecutan <instrucciones> para cada uno de los elementos de la <colección> indicada. <elemento> es una variable de sólo lectura de tipo <tipoElemento> que almacenará en cada momento el elemento de la colección que se esté procesando y que podrá ser accedida desde <instrucciones>.

    Es importante señalar que <colección> no puede valer null porque entonces saltaría una excepción de tipo System.NullReferenceException, y que <tipoElemento> ha de ser un tipo cuyos objetos puedan almacenar los valores de los elementos de <colección>

    En tanto que una tabla se considera que es una colección, el siguiente código muestra cómo usar for para compactar aún más el código de la clase HolaMundoFor anterior:


using System;
class HolaMundoForeach
{
    public static void Main(String[] args)
    {
       if (args.Length > 0)
          foreach(String arg in args) {
           Console.WriteLine("¡Hola {0}!", arg);
          } 
       else
          Console.WriteLine("¡Hola mundo!");
    }

    Las tablas multidimensionales también pueden recorrerse mediante el foreach, el cual pasará por sus elementos en orden tal y como muestra el siguiente fragmento de código:


int[,] tabla = { {1,2}, {3,4} };
foreach (int elemento in tabla)
    Console.WriteLine(elemento);

    Cuya salida por pantalla es:


 1
 2
 3
 4

    En general, se considera que una colección es todo aquel objeto que implemente las interfaces IEnumerable o IEnumerator del espacio de nombres System.Collections de la BCL, que están definidas como sigue:


interface IEnumerable
{
  [C#]
  IEnumerator GetEnumerator();
}
interface IEnumerator
{
  [C#]
  object Current {get;}
  [C#]
  bool MoveNext();
  [C#]
  void Reset();               
}

    El método Reset() ha de implementarse de modo que devuelva el enumerador reiniciado a un estado inicial donde aún no referencie ni siquiera al primer elemento de la colección sino que sea necesario llamar a MoveNext() para que lo haga.

    El método MoveNext() se ha de implementar de modo que haga que el enumerador pase a apuntar al siguiente elemento de la colección y devuelva un booleano que indique si tras avanzar se ha alcanzado el final de la colección.

    La propiedad Current se ha de implementar de modo que devuelva siempre el elemento de la colección al que el enumerador esté referenciando. Si se intenta leer Current habiéndose ya recorrido toda la colección o habiéndose reiniciado la colección y no habiéndose colocado en su primer elemento con MoveNext(), se ha de producir una excepción de tipo System.Exception.SystemException.InvalidOperationException

    Otra forma de conseguir que foreach considere que un objeto es una colección válida consiste en hacer que dicho objeto siga el patrón de colección. Este patrón consiste en definir el tipo del objeto de modo que sus objetos cuenten con un método público GetEnumerator() que devuelva un objeto no nulo que cuente con una propiedad pública llamada Current que permita leer el elemento actual y con un método público bool MoveNext() que permita cambiar el elemento actual por el siguiente y devuelva false sólo cuando se haya llegado al final de la colección.

    El siguiente ejemplo muestra ambos tipos de implementaciones:


using System;
using System.Collections;
class Patron
{
 private int actual = -1;
 public Patron GetEnumerator()
 {
  return this;
 }  
 
 public int Current
 {
  get {return actual;}
 }
 
 public bool MoveNext()
 {
  bool resultado = true;
  actual++;
  if (actual==10)
      resultado = false;
  return resultado;
 }
}

class Interfaz:IEnumerable,IEnumerator
{
 private int actual = -1;
 public object Current
 {
  get {return actual;}
 }
 
 public bool MoveNext()
 {
   bool resultado = true;
   actual++;
   if (actual==10)
     resultado = false;
   return resultado;
 }
 
 public IEnumerator GetEnumerator()
 {
   return this;
 }
 
 public void Reset()
 {
   actual = -1;
 }
}
class Principal
{
 public static void Main()
 {
    Patron obj = new Patron();
    Interfaz obj2 = new Interfaz();
    foreach (int elem in obj)
       Console.WriteLine(elem);
    foreach (int elem in obj2)
       Console.WriteLine(elem);
 }
}

    Nótese que en realidad en este ejemplo no haría falta implementar IEnumerable, puesto que la clase Interfaz ya implementa IEnumerator y ello es suficiente para que pueda ser recorrida mediante foreach.

    La utilidad de implementar el patrón colección en lugar de la interfaz IEnumerable es que así no es necesario que Current devuelva siempre un object, sino que puede devolver objetos de tipos más concretos y gracias a ello puede detectarse al compilar si el <tipoElemento> indicado puede o no almacenar los objetos de la colección.

    Por ejemplo, si en el ejemplo anterior sustituimos en el último foreach el <tipoElemento> indicado por Patrón, el código seguirá compilando pero al ejecutarlo saltará una excepción System.InvalidCastException. Sin embargo, si la sustitución se hubiese hecho en el penúltimo foreach, entonces el código directamente no compilaría y se nos informaría de un error debido a que los objetos int no son convertibles en objetos Patrón.

    También hay  que tener en cuenta que la comprobación de tipos que se realiza en tiempo de ejecución si el objeto sólo implementó la interfaz IEnumerable es muy estricta, en el sentido de que si en el ejemplo anterior sustituimos el <tipoElemento> del último foreach por byte también se lanzará la excepción al no ser los  objetos de tipo int implícitamente convertibles en bytes sino sólo a través del operador () Sin embargo, cuando se sigue el patrón de colección las comprobaciones de tipo no son tan estrictas y entonces sí que sería válido sustituir int por byte en <tipoElemento>.

    El problema de sólo implementar el patrón colección es que este es una característica propia de C# y con las instrucciones foreach (o equivalentes) de lenguajes que no lo soporten no se podría recorrer colecciones que sólo siguiesen este patrón. Una solución en estos casos puede ser hacer que el tipo del objeto colección implemente tanto la interfaz IEnumerable como el patrón colección. Obviamente esta interfaz debería implementarse explícitamente para evitarse conflictos derivados de que sus miembros tengan signaturas coincidentes con las de los miembros propios del patrón colección.

    Si un objeto de un tipo colección implementa tanto la interfaz IEnumerable como el patrón de colección, entonces en C# foreach usará el patrón colección para recorrerlo.

Estrcuturas de control.
José Antonio González Seco

José Antonio es experto en tecnologias Microsoft. Imparte cursos y conferencias en congresos sobre C# y .NET en Universidades de toda España (Sevilla, Barcelona, San Sebastián, Valencia, Oviedo, etc.) en representación de grandes empresas como Microsoft.
Fecha de alta:03/10/2006
Última actualizacion:03/10/2006
Visitas totales:78202
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com