Zona HTML Zona Java Zona PHP Zona ASP Zona Bases de datos
Inicio > Tutoriales > Lenguajes orientados a objeto > C# > El lenguaje de programación C#
-Tutoriales

El lenguaje de programación C#


Tema 16: Instrucciones

. Concepto de instrucción

Toda acción que se pueda realizar en el cuerpo de un método, como definir variables locales, llamar a métodos, asignaciones y muchas cosas más que veremos a lo largo de este tema, son instrucciones.

Las instrucciones se agrupan formando bloques de instrucciones, que son listas de instrucciones encerradas entre llaves que se ejecutan una tras otra. Es decir, la sintaxis que se sigue para definir un bloque de instrucciones es:

{ <listaInstrucciones> }

Toda variable que se defina dentro de un bloque de instrucciones sólo existirá dentro de dicho bloque. Tras él será inaccesible y podrá ser destruida por el recolector de basura. Por ejemplo, este código no es válido:

public void f();
{
  { int b; }
  b = 1;       // ERROR: b no existe fuera del bloque donde se declaró.
}

Los bloques de instrucciones pueden anidarse, aunque si dentro de un bloque interno definimos una variable con el mismo nombre que otra definida en un bloque externo se considerará que se ha producido un error, ya que no se podrá determinar a cuál de las dos se estará haciendo referencia cada vez que se utilice su nombre en el bloque interno.

. Instrucciones básicas

. Definiciones de variables locales

En el Tema 7:Variables y tipos de datos ya se vió que las variables locales son variables que se definen en el cuerpo de los métodos y sólo son accesibles desde dichos cuerpos. Recuérdese que la sintaxis explicada para definirlas era la siguiente:

<modificadores> <tipoVariable> <nombreVariable>  = <valor>;

También se vió que podían definirse varias variables en una misma instrucción separando sus pares nombre-valor mediante comas. Por ejemplo:

int a=5, b, c=-1;

. Asignaciones

Una asignación es simplemente una instrucción mediante la que se indica un valor a almacenar en un dato. La sintaxis usada para ello es:

<destino> = <origen>;

En temas previos ya se han dado numerosos ejemplos de cómo hacer esto, por lo que no es necesario hacer ahora mayor hincapié en ello.

. Llamadas a métodos

En el Tema 8: Métodos ya se explicó que una llamada a un método consiste en solicitar la ejecución de sus instrucciones asociadas dando a sus parámetros ciertos valores. Si el método a llamar es una método de objeto, la sintaxis usada para ello es:

<objeto>.<nombreMétodo>(<valoresParámetros>);

Y si el método a llamar es un método de tipo, entonces la llamada se realiza con:

<nombreTipo>.<nombreMétodo>(<valoresParámetros>);

Recuérdese que si la llamada al método de tipo se hace dentro de la misma definición de tipo donde el método fue definido, la sección <nombreTipo>. de la sintaxis es opcional.

. Instrucción nula

La instrucción nula es una instrucción que no realiza nada en absoluto. Su sintaxis consiste en escribir un simple punto y coma para representarla. O sea, es:

    ;
  

Suele usarse cuando se desea indicar explícitamente que no se desea ejecutar nada. Usarla es útil para facilitar la legibilidad del código o, como veremos más adelante en el tema, porque otras instrucciones la necesitan para indicar cuándo en algunos de sus bloques de instrucciones componentes no se ha de realizar ninguna acción.

. Instrucciones condicionales

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 <expresión>. Si su valor es <valor1> se ejecuta el <bloque1>, si es <valor2> se ejecuta <bloque2>, 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 <bloqueDefault>; 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 usarse 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 la <instrucciones> si es cierta o pasándose a ejecutar la instrucción siguiente alwhile 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 anteriro 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 HolaMundoFoeach
{
  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!");
  }
}

En general, se considera que una colección es todo aquel objeto que implemente la interfaz System.Collections.IEnumerable. Esta interfaz está definida en la BCL así:

interface IEnumerable
{
   IEnumerator GetEnumerator();
}

El objeto de interfaz System.Collections.IEnumerator devuelto ha de ser un enumerador que permita recorrer los elementos de la <colección>. Dicha interfaz está así predefinida:

interface IEnumerator
{
   object Current {get;}
   bool MoveNext();
   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);
  }
}

El tipo System.Array implementa la interfaz System.Collectiones.IEnumerator, por lo que todas las tablas podrán ser usadas recorridas con foreach. Si la tabla a recorrer es multidimensional, sus elementos se recorrerán en orden como muestra este ejemplo:

int[,] tabla = { {1,2}, {3,4} };

foreach (int elemento in tabla)
  Console.WriteLine(elemento);

La salida por pantalla del fragmento de código anterior será:

1
2
3
4

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.

. Instrucciones de excepciones

. Concepto de excepción.

Las excepciones son el mecanismo recomendado en la plataforma .NET para la propagación de errores que se produzcan durante la ejecución de las aplicaciones (divisiones por cero, intentos de lectura de archivos dañados, etc.) Básicamente una excepción es un objeto derivado de System.Exception que se genera cuando en tiempo de ejecución se produce algún error y que contiene información sobre el mismo.

Tradicionalmente, el sistema que en otros lenguajes y plataformas se ha venido usando para informar estos errores consistía simplemente en hacer que los métodos en cuya ejecución pudiesen producirse devolvieran códigos que informasen sobre si se han ejecutado correctamente o, en caso contrario, sobre cuál fue el error producido. Sin embargo, las excepciones proporcionan las siguientes ventajas frente a dicho sistema:

  • Claridad: El uso de códigos especiales para informar de error suele dificultar la legibilidad del fuente en tanto que se mezclan las instrucciones propias de la lógica del mismo con las instrucciones propias del tratamiento de los errores que pudiesen producirse durante su ejecución. Por ejemplo:
    int resultado = obj.Método();
    if (resultado == 0)  // Sin errores al ejecutar obj.Método();
    {...}
    else if (resultado == 1)  // Tratamiento de error de código 1
    {...}
    else if (resultado == 2) // Tratamiento de error de código 2
    ...

    Como se verá, utilizando excepciones es posible escribir el código como si nunca se fuesen a producir errores y dejar en una zona aparte todo el código de tratamiento de errores, lo que contribuye a facilitar la legibilidad de los fuentes.

  • Más información: A partir del valor de un código de error puede ser difícil deducir las causas del mismo y conseguirlo muchas veces implica tenerse que consultar la documentación que proporcionada sobre el método que lo provocó, que puede incluso que no especifique claramente su causa.

    Por el contrario, una excepción es un objeto que cuenta con campos que describen las causas del error y a cuyo tipo suele dársele un nombre que resuma claramente su causa. Por ejemplo, para informar errores de división por cero se suele utilizar una excepción predefinida de tipo DivideByZeroException en cuyo campo Message se detallan las causas del error producido

  • Tratamiento asegurado: Cuando se utilizan códigos de error nada obliga a tratarlos en cada llamada al método que los pueda producir, e ignorarlos puede provocar más adelante en el código comportamientos inesperados de causas difíciles de descubrir.

    Cuando se usan excepciones siempre se asegura que el programador trate toda excepción que pueda producirse o que, si no lo hace, se aborte la ejecución de la aplicación mostrándose un mensaje indicando dónde se ha producido el error.

Ahora bien, tradicionalmente en lenguajes como C++ el uso de excepciones siempre ha tenido las desventajas respecto al uso de códigos de error de complicar el compilador y dar lugar a códigos más lentos y difíciles de optimizar en los que tras cada instrucción que pudiese producir excepciones el compilador debe introducir las comprobaciones necesarias para detectarlas y tratarlas así como para comprobar que los objetos creados sean correctamente destruidos si se producen.

Sin embargo, en la plataforma .NET desaparacen los problemas de complicar el compilador y dificultar las optimizaciones ya que que es el CLR quien se encarga de detectar y tratar las excepciones y es su recolector de basura quien se encarga asegurar la correcta destrucción de los objetos. Obviamente el código seguirá siendo algo más lento, pero es un pequeño sacrificio que merece la pena hacer en tanto que ello asegura que nunca se producirán problemas difíciles de detectar derivados de errores ignorados.

. La clase System.Exception

Como ya se ha dicho, todas las excepciones derivan de un tipo predefinido en la BCL llamado System.Exception. Los principales miembros que heredan de éste son:

  • string Message {virtual get;}: Contiene un mensaje descriptivo de las causas de la excepción. Por defecto este mensaje es una cadena vacía ("")
  • Exception InnerException {virtual get;}: Si una excepción fue causada como consecuencia de otra, esta propiedad contiene el objeto System.Exception que representa a la excepción que la causó. Así se pueden formar cadenas de excepciones de cualquier longitud. Si se desea obtener la última excepción de la cadena es mejor usar el método virtual Exception GetBaseException()
  • string StackTrace {virtual get;}: Contiene la pila de llamadas a métodos que se tenía en el momento en que se produjo la excepción. Esta pila es una cadena con información sobre cuál es el método en que se produjo la excepción, cuál es el método que llamó a este, cuál es el que llamó a ese otro, etc.
  • string Source {virtual get; virtual set;}: Almacena información sobre cuál fue la aplicación u objeto que causó la excepción.
  • MethodBase TargetSite {virtual get;}: Almacena cuál fue el método donde se produjo la excepción en forma de objeto System.Reflection.MethodBase. Puede consultar la documentación del SDK si desea cómo obtener información sobre las características del método a través del objeto MethodBase.
  • string HelpLink {virtual get;}: Contiene una cadena con información sobre cuál es la URI donde se puede encontrar información sobre la excepción. El valor de esta cadena puede establecerse con virtual Exception SetHelpLink (string URI), que devuelve la excepción sobre la que se aplica pero con la URI ya actualizada.

Para crear objetos de clase System.Exception se puede usar los constructores:

    Exception()
Exception(string msg)
Exception(string msg, Exception causante)
  

El primer constructor crea una excepción cuyo valor para Message será "" y no causada por ninguna otra excepción (InnerException valdrá null) El segundo la crea con el valor indicado para Message, y el último la crea con además la excepción causante indicada.

En la práctica, cuando se crean nuevos tipos derivados de System.Exception no se suele redefinir sus miembros ni añadirles nuevos, sino que sólo se hace la derivación para distinguir una excepciones de otra por el nombre del tipo al que pertenecen. Ahora bien, es conveniente respetar el convenio de darles un nombre acabado en Exception y redefinir los tres constructores antes comentados.

. Excepciones predefinidas comunes

En el espacio de nombres System de la BCL hay predefinidas múltiples excepciones derivadas de System.Exception que se corresponden con los errores más comunes que pueden surgir durante la ejecución de una aplicación. En la Tabla 8 se recogen algunas:

Tipo de la excepción Causa de que se produzca la excepción
ArgumentException Pasado argumento no válido (base de excepciones de argumentos)
ArgumentNullException Pasado argumento nulo
ArgumentOutOfRangeException Pasado argumento fuera de rango
ArrayTypeMistmatchException Asignación a tabla de elemento que no es de su tipo
COMException Excepción de objeto COM
DivideByZeroException División por cero
IndexOutOfRangeException Índice de acceso a elemento de tabla fuera del rango válido (menor que cero o mayor que el tamaño de la tabla)
InvalidCastException Conversión explícita entre tipos no válida
InvalidOperationException Operación inválida en estado actual del objeto
InteropException Base de excepciones producidas en comunicación con código inseguro
NullReferenceException Acceso a miembro de objeto que vale null
OverflowException Desbordamiento dentro de contexto donde se ha de comprobar los desbordamientos (expresión constante, instrucción checked, operanción checked u opción del compilador /checked)
OutOfMemoryException Falta de memoria para crear un objeto con new
SEHException Excepción SHE del API Win32
StackOverflowException Desbordamiento de la pila, generalmente debido a un excesivo número de llamadas recurrentes.
TypeInizializationException Ha ocurrido alguna excepción al inicializar los campos estáticos o el constructor estático de un tipo. En InnerException se indica cuál es.

Tabla 8: Excepciones predefinidas de uso frecuente

Obviamente, es conveniente que si las aplicaciones que escribamos necesiten lanzar excepciones relativas a errores de los tipos especificados en la Tabla 8, lancen precisamente las excepciones indicadas en esa tabla y no cualquier otra - ya sea definida por nosotros mismos o predefinida en la BCL con otro significado.

. Lanzamiento de excepciones. Instrucción throw

Para informar de un error no basta con crear un objeto del tipo de excepción apropiado, sino que también hay pasárselo al mecanismo de propagación de excepciones del CLR. A esto se le llama lanzar la excepción, y para hacerlo se usa la siguiente instrucción:

    throw <objetoExcepciónALanzar>;
  

Por ejemplo, para lanzar una excepción de tipo DivideByZeroException se podría hacer:

throw new DivideByZeroException();

Si el objeto a lanzar vale null, entonces se producirá una NullReferenceException que será lanzada en vez de la excepción indicada en la instrucción throw.

. Captura de excepciones. Instrucción try

Una vez lanzada una excepción es posible escribir código que es encarge de tratarla. Por defecto, si este código no se escribe la excepción provoca que la aplicación aborte mostrando un mensaje de error en el que se describe la excepción producida (información de su propiedad Message) y dónde se ha producido (información de su propiedad StackTrace) Así, dado el siguiente código fuente de ejemplo:

using System;

class PruebaExcepciones
{
  static void Main()
  {
    A obj1 = new A();
    obj1.F();
  }
}

class A
{
  public void F()
  {
    G();
  }
  
  static public void G()
  {
    int c = 0;
    int d = 2/c;
  }
}

Al compilarlo no se detectará ningún error ya que al compilador no le merece la pena calcular el valor de c en tanto que es una variable, por lo que no detectará que dividir 2/c no es válido. Sin embargo, al ejecutarlo se intentará dividir por cero en esa instrucción y ello provocará que aborte la aplicación mostrando el siguiente mensaje:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at PruebaExcepciones.Main()

Como se ve, en este mensaje se indica que no se ha tratado una excepción de división por cero (tipo DivideByZeroException) dentro del código del método Main() del tipo PruebaExcepciones. Si al compilar el fuente hubiésemos utilizado la opción /debug, el compilador habría creado un fichero .pdb con información extra sobre las instrucciones del ejecutable generado que permitiría que al ejecutarlo se mostrase un mensaje mucho más detallado con información sobre la instrucción exacta que provocó la excepción, la cadena de llamadas a métodos que llevaron a su ejecución y el número de línea que cada una ocupa en el fuente:

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at A.G() in E:\c#\Ej\ej.cs:line 22
   at A.F() in E:\c#\Ej\ej.cs:line 16
   at PruebaExcepciones.Main() in E:\c#\Ej\ej.cs:line 8

Si se desea tratar la excepción hay que encerrar la división dentro de una instrucción try con la siguiente sintaxis:

try
  <instrucciones>
catch (<excepción1>)
  <tratamiento1>
catch (<excepción2>)
  <tratamiento2>
...
finally
  <instruccionesFinally>

El significado de try es el siguiente: si durante la ejecución de las <instrucciones> se lanza una excepción de tipo <excepción1> (o alguna subclase suya) se ejecutan las instrucciones <tratamiento1>, si fuese de tipo <excepción2> se ejecutaría <tratamiento2>, y así hasta que se encuentre una cláusula catch que pueda tratar la excepción producida. Si no se encontrase ninguna y la instrucción try estuviese anidada dentro de otra, se miraría en los catch de su try padre y se repetiría el proceso. Si al final se recorren todos los trys padres y no se encuentra ningún catch compatible, entonces se buscaría en el código desde el que se llamó al método que produjo la excepción. Si así se termina llegando al método que inició el hilo donde se produjo la excepción y tampoco allí se encuentra un tratamiento apropiado se aborta dicho hilo; y si ese hilo es el principal (el que contiene el punto de entrada) se aborta el programa y se muestra el mensaje de error con información sobre la excepción lanzada ya visto.

Así, para tratar la excepción del ejemplo anterior de modo que una división por cero provoque que a d se le asigne el valor 0, se podría reescribir G() de esta otra forma:

static public void G()
{
  try
  {
    int c = 0;
    int d = 2/c;
  }
  catch (DivideByZeroException)
  { d=0; }
}

Para simplificar tanto el compilador como el código generado y favorecer la legibilidad del fuente, en los catchs se busca siempre orden de aparación textual, por lo que para evitar catchs absurdos no se permite definir catchs que puedan capturar excepciones capturables por catchs posteriores a ellos en su misma instrucción try.

También hay que señalar que cuando en <instrucciones> se lance una excepción que sea tratada por un catch de algún try -ya sea de la que contiene las <instrucciones>, de algún try padre suyo o de alguno de los métodos que provocaron la llamada al que produjo la excepción- se seguirá ejecutando a partir de las instrucciones siguientes a ese try.

El bloque finally es opcional, y si se incluye ha de hacerlo tras todas los bloques catch. Las <instruccionesFinally> de este bloque se ejecutarán tanto si se producen excepciones en <instrucciones> como si no. En el segundo caso sus instrucciones se ejecutarán tras las <instrucciones>, mientras que en el primero lo harán después de tratar la excepción pero antes de seguirse ejecutando por la instrucción siguiente al try que la trató. Si en un try no se encuentra un catch compatible, antes de pasar a buscar en su try padre o en su método llamante padre se ejecutarán las <instruccionesFinally>.

Sólo si dentro de un bloque finally se lanzase una excepción se aborta la ejecución del mismo. Dicha excepción sería propagada al try padre o al método llamante padre del try que contuviese el finally.

Aunque los bloques catch y finally son opcionales, toda instrucción try ha de incluir al menos un bloque catch o un bloque finally.

El siguiente ejemplo resume cómo funciona la propagación de excepciones:

using System;

class MiException:Exception {}

class Excepciones
{
  public static void Main()
  {
    try
    {
      Console.WriteLine("En el try de Main()");
      Método();
      Console.WriteLine("Al final del try de Main()");
    }
    catch (MiException)
    {
      Console.WriteLine("En el catch de Main()");
    }
    finally
    {
      Console.WriteLine("finally de Main()");
    }
  }
  
  public static void Método()
  {
    try
    {
      Console.WriteLine("En el try de Método()");
      Método2();
      Console.WriteLine("Al final del try de Método()");
    }
    catch (OverflowException)
    {
      Console.WriteLine("En el catch de Método()");
    }
    finally
    {
      Console.WriteLine("finally de Método()");
    }
  }
  
  public static void Método2()
  {
    try
    {
      Console.WriteLine("En el try de Método2()");
      throw new MiException();
      Console.WriteLine("Al final del try de Método2()");
    }
    catch (DivideByZeroException)
    { Console.WriteLine("En el catch de Método2()"); }
    finally
    { Console.WriteLine("finally de Método2()"); }
  }
}

Nótese que en este código lo único que se hace es definir un tipo nuevo de excepción llamado MiException y llamarse en el Main() a un método llamado Método() que llama a otro de nombre Método2() que lanza una excepción de ese tipo. Viendo la salida de este código es fácil ver el recorrido seguido durante la propagación de la excepción:

En try de Main()
En try de Método()
En try de Método2()
finally de Método2
finally de Método
En catch de Main()
finally de Main()

Como se puede observar, hay muchos WriteLine() que nunca se ejecutan ya que en cuento se lanza una excepción se sigue ejecutando tras la instrucción siguiente al try que la trató (aunque ejecutando antes los finally pendientes, como se deduce de la salida del ejemplo) De hecho, el compilador se dará cuenta que la instrucción siguiente al throw nunca se ejecutará e informará de ello con un mensaje de aviso.

La idea de todo este mecanismo de excepciones es evitar mezclar el código normal con el código de tratamiento de errores. Así, en <instrucciones> se escibiría el código como si no se pudiesen producir errores, en las cláusulas catch se tratarían los posibles errores, y en la cláusula finally se incluiría código a ejecutar tanto si produjesen errores como si no (suele usarse para liberar recursos ocupados, como fichero o conexiones de red abiertas)

En realidad, también es posible escribir cada cláusula catch definiendo una variable que se podrá usar dentro del código de tratamiento de la misma para hacer referencia a la excepción capturada. Esto se hace con la sintaxis:

catch (<tipoExcepción> <nombreVariable>)
{
  <tratamiento>
}

Nótese que en tanto que todas las excepciones derivan de System.Exception, para definir una cláusula catch que pueda capturar cualquier tipo de excepción basta usar:

catch(System.Exception <nombreObjecto>)
{
  <tratamiento>
}

En realidad la sintaxis anterior sólo permite capturar las excepciones propias de la plataforma .NET, que derivan de System.Exception. Sin embargo, hay lenguajes como C++ que permiten lanzar excepciones no derivadas de dicha clase, y para esos casos se ha incluido en C# una variante de catch sí que realmente puede capturar excepciones de cualquier tipo, tanto si derivan de System.Exception como si no. Su sintaxis es:

catch
{
 <tratamiento>
}

Como puede deducirse de su sintaxis, el problema que presenta esta última variante de catch es que no proporciona información sobre cuál es la excepción capturada, por lo que a veces puede resultar poco útil y si sólo se desea capturar cualquier excepción derivada de System.Exception es mejor usar la sintaxis explicada previamente a ella.

En cualquier casos, ambos tipos de cláusulas catch sólo pueden ser escritas como la última cláusula catch del try, ya que si no las cláusulas catch que le siguiesen nunca llegarían a ejecutarse debido a que las primeras capturarían antes cualquier excepción derivada de System.Exception.

Respecto al uso de throw, hay que señalar que hay una forma extra de usarlo que sólo es válida dentro de códigos de tratamiento de excepciones (códigos <tratamientoi> de las cláusulas catch) Esta forma de uso consiste en seguir simplemente esta sintaxis:

throw;

En este caso lo que se hace es relanzar la misma excepción que se capturó en el bloque catch dentro de cuyo de código de tratamiento se usa el throw; Hay que precisar que la excepción relanzada es precisamente la capturada, y aunque en el bloque catch se la modifique a través de la variable que la repreesnta, la versión relanzada será la versión original de la misma y no la modificada.

Además, cuando se relance una excepción en un try con cláusula finally, antes de pasar a reprocesar la excepción en el try padre del que la relanzó se ejecutará dicha cláusula.

. Instrucciones de salto

Las instrucciones de salto permiten ejecutar variar el orden normal en que se ejecutan las instrucciones de un programa, que consiste en ejecutarlas una tras otra en el mismo orden en que se hubiesen escrito en el fuente. En los subapartados de este epígrafe se describirán cuáles son las instrucciones de salto incluidas en C#:

. Instrucción break

Ya se ha visto que la instrucción break sólo puede incluirse dentro de bloques de instrucciones asociados a instrucciones iterativas o instrucciones switch e indica que se desea abortar la ejecución de las mismas y seguir ejecutando a partir de la instrucción siguiente a ellas. Se usa así:

break;

Cuando esta sentencia se usa dentro de un try con cláusula finally, antes de abortarse la ejecución de la instrucción iterativa o del switch que la contiene y seguirse ejecutando por la instrucción que le siga, se ejecutarán las instrucciones de la cláusula finally del try. Esto se hace para asegurar que el bloque finally se ejecute aún en caso de salto.

Además, si dentro una cláusula finally incluida en de un switch o de una instrucción iterativa se usa break, no se permite que como resultado del break se salga del finally.

. Instrucción continue

Ya se ha visto que la instrucción continue sólo puede usarse dentro del bloque de instrucciones de una instrucción iterativa e indica que se desea pasar a reevaluar directamente la condición de la misma sin ejecutar el resto de instrucciones que contuviese. La evaluación de la condición se haría de la forma habitual: si es cierta se repite el bucle y si es falsa se continúa ejecutando por la instrucción que le sigue. Su sintaxis de uso es así de sencilla:

continue;

En cuanto a sus usos dentro de sentencias try, tiene las mismas restricciones que break: antes de salir de un try se ejecutará siempre su bloque finally y no es posible salir de un finally incluido dentro de una instrucción iterativa como consecuencia de un continue.

. Instrucción return

Esta instrucción se usa para indicar cuál es el objeto que ha de devolver un método, y se usa así:

return <objetoRetorno>;

La ejecución de esta instrucción provoca que se aborte la ejecución del método dentro del que aparece y que se devuelva el <objetoRetorno> al método que lo llamó. Como es lógico, este objeto ha de ser del tipo de retorno del método en que aparece el return o de alguno compatible con él, por lo que esta instrucción sólo podrá incluirse en métodos cuyo tipo de retorno no sea void, o en los bloques get de las propiedades o indizadores. De hecho, es obligatorio que todo método con tipo de retorno termine por un return.

Los métodos que devuelvan void pueden tener un return con una sintaxis espacial en la que no se indica ningún valor a devolver sino que simplemente se usa return para indicar que se desea terminar la ejecución del método:

return;

Nuevamente, como con el resto de instrucciones de salto hasta ahora vistas, si se incluyese un return dentro de un bloque try con cláusula finally, antes de devolverse el objeto especificado se ejecutarían las instrucciones de la cláusula finally. Si hubiesen varios bloques finally anidados, las instrucciones de cada uno es ejecutarían de manera ordenada (o sea, del más interno al más externo) Ahora bien, lo que no es posible es incluir un return dentro de una cláusula finally.

. Instrucción goto

La instrucción goto permite pasar a ejecutar el código a partir de una instrucción cuya etiqueta se indica en el goto. La sintaxis de uso de esta instrucción es:

goto <etiqueta>;

Como en la mayoría de los lenguajes, goto es una instrucción maldita cuyo uso no se recomienda porque dificulta innecesariamente la legibilidad del código y suele ser fácil simularla usando instrucciones iterativas y selectivas con las condiciones apropiadas. Sin embargo, en C# se incluye porque puede ser eficiente usarla si se anidan muchas instrucciones y para reducir sus efectos negativos se le han impuesto unas restricciones:

Sólo se pueden etiquetar instrucciones, y no a directivas using, directivas de preprocesado, definiciones de miembros, de tipos o de espacios de nombres.

  • La etiqueta indicada no pueda pertenecer a un bloque de instrucciones anidado dentro del bloque desde el que se usa el goto ni que etiquete a instrucciones de otro método diferente a aquél en el cual se encuentra el goto que la referencia.
  • Para etiquetar una instrucción de modo que pueda ser destino de un salto con goto basta precederla del nombre con el que se la quiera etiquetar seguido de dos puntos (:) Por ejemplo, el siguiente código demuestra cómo usar goto y definir una etiqueta:
using System;

class HolaMundoGoto
{
  public static void Main(string[] args)
  {
    for (int i=0; i<args.Length; i++)
    {
      if (args[i] != "salir")
        Console.WriteLine(args[i]);
      else
        goto fin:
    }
    fin: ;
  }
}

Este programa de ejemplo lo que hace es mostrar por pantalla todos los argumentos que se le pasen como parámetros, aunque si alguno fuese salir entonces se dejaría de mostrar argumentos y se aborta la ejecución de la aplicación. Véase además que este ejemplo pone de manifiesto una de las utilidades de la instrucción nula, ya que si no se hubiese escrito tras la etiqueta fin el programa no compilaría en tanto que toda etiqueta ha de preceder a alguna instrucción (aunque sea la instrucción nula)

Nótese que al fin y al cabo los usos de goto dentro de instrucciones switch que se vieron al estudiar dicha instrucción no son más que variantes del uso general de goto, ya que default: no es más que una etiqueta y case <valor>: puede verse como una etiqueta un tanto especial cuyo nombre es case seguido de espacios en blanco y un valor. En ambos casos, la etiqueta indicada ha de pertenecer al mismo switch que el goto usado y no vale que éste no la contenga pero la contenga algún switch que contenga al switch del goto.

El uso de goto dentro de sentencias try, tiene las mismas restricciones que break, continue y return: antes de salir con un goto de un try se ejecutará siempre su bloque finally y no es posible forzar a saltar fuera de un finally.

. Instrucción throw

La instrucción throw ya se ha visto que se usa para lanzar excepciones de este modo:

throw <objetoExcepciónALanzar>;

En caso de que no se indique ningún <objetoExcepciónALanzar> se relanzará el que se estuviese tratando en ese moment, aunque esto sólo es posible si el throw se ha escrito dentro del código de tratamiento asociado a alguna cláusula catch.

Como ya se ha explicado a fondo esta instrucción en este mismo tema, para más información sobre basta remitirse al epígrafe Excepciones de este tema.

. Otras instrucciones

Las instrucciones vistas hasta ahora son comunes a muchos lenguajes de programación. Sin embargo, en C# también se ha incluido un buen número de nuevas instrucciones propias de este lenguaje. Estas instrucciones se describen en los siguientes apartados:

. Instrucciones checked y unchecked

Las instrucciones checked y unchecked permiten controlar la forma en que tratarán los desbordamientos que ocurran durante la realización de operaciones aritméticas con tipos básico enteros. Funcionan de forma similar a los operadores checked y unchecked ya vistos en el Tema 4: Aspectos léxicos, aunque a diferencia de éstos son aplicables a bloques enteros de instrucciones y no a una única expresión. Así, la instrucción checked se usa de este modo:

checked
  <instrucciones>

Todo desbordamiento que se produzca al realizar operaciones aritméticas con enteros en <instrucciones> provocará que se lance una excepción System.OverflowException. Por su parte, la instrucción unchecked se usa así:

unchecked
  <instrucciones>

En este caso, todo desbordamiento que se produzca al realizar operaciones aritméticas con tipos básicos enteros en <instrucciones> será ignorado y lo que se hará será tomar el valor resultante de quedarse con los bits menos significativos necesarios.

Por defecto, en ausencia de estas instrucciones las expresiones constantes se evalúan como si se incluyesen dentro de una instrucción checked y las que no constantes como si se incluyesen dentro de una instrucción unchecked. Sin embargo, a través de la opción /checked del compilador es posible tanto hacer que por defecto se comprueben los desbordamiento en todos los casos para así siempre poder detectarlos y tratarlos

Desde Visual Studio.NET, la forma de controlar el tipo de comprobaciones que por defecto se harán es a través de View -> Propety Pages -> Configuration Settings -> Build -> Check for overflow underflow.

El siguiente código muestra un ejemplo de cómo usar ambas instrucciones:

using System;

class Unchecked
{
  static short x = 32767;   // Valor maximo del tipo short
  
  public static void Main()
  {
    unchecked
    {
      Console.WriteLine((short) (x+1));  // (1)
      Console.WriteLine((short) 32768); // (2)
    }
  }
}

En un principio este código compilaría, pero los desbordamientos producidos por el hecho de que 32768 no es un valor que se pueda representar con un short (16 bits con signo) provocaría que apareciese por pantalla dicho valor truncado, mostrándose:

-32768
-32678

Sin embargo, si sustituyésemos la instrucción unchecked por checked, el código anterior ni siquiera compilaría ya que el compilador detectaría que se va a producir un desbordamiento en (2) debido a que 32768 es constante y no representable con un short.

Si eliminamos la instrucción (2) el código compilaría ya que (x+1) no es una expresión constante y por tanto el compilador no podría detectar desbordamiento al compilar. Sin embargo, cuando se ejecutase la aplicación se lanzaría una System.OverflowException.

. Instrucción lock

La instrucción lock es útil en aplicaciones concurrentes donde múltiples hilos pueden estar accediendo simultáneamente a un mismo recurso, ya que lo que hace es garantizar que un hilo no pueda acceder a un recurso mientras otro también lo esté haciendo. Su sintaxis es la siguiente:

lock (<objeto>)
  <instrucciones>

Su significado es el siguiente: ningún hilo puede ejecutar las <instrucciones> del bloque indicado si otro las está ejecutando, y si alguno lo intenta se quedará esperando hasta que acabe el primero. Esto también afecta a bloques de <instrucciones> de cualquier otro lock cuyo <objeto> sea el mismo. Este <objeto> ha de ser de algún tipo referencia.

En realidad, la instrucción anterior es equivalente a hacer:

System.Threading.Monitor.Enter(<objeto>);
try
  <instrucciones>
finally
{
  System.Threading.Monitor.Exit(<objeto>);
}

Sin embargo, usar lock tiene dos ventajas: es más compacto y eficiente (<objeto> sólo se evalúa una vez)

Una buena forma de garantizar la exclusión mutua durante la ejecución de un método de un cierto objeto es usando this como <objeto>. En el caso de que se tratase de un método de tipo, en tanto que this no tiene sentido dentro de estos métodos estáticos una buena alternativa sería usar el objeto System.Type que representase a ese tipo. Por ejemplo:

class C
{
  public static void F()
  {
    lock(typeof(C))
    {
      // ... Código al que se accede exclusivamente
    }
  }
}

. Instrucción using

La instrucción using facilita el trabajo con objetos que tengan que ejecutar alguna tarea de limpieza o liberación de recursos una vez que termine de ser útiles. Aunque para estos menesteres ya están los destructores, dado su carácter indeterminista puede que en determinadas ocasiones no sea conveniente confiar en ellos para realizar este tipo de tareas. La sintaxis de uso de esta instrucción es la siguiente:

using (<tipo> <declaraciones>)
  <instrucciones>

En <declaraciones> se puede indicar uno o varios objetos de tipo <tipo> separados por comas. Estos objetos serán de sólo lectura y sólo serán accesibles desde <instrucciones>. Además, han de implementar la interfaz System.IDisposable definida como sigue:

interface IDisposable
{
  void Dispose()
}

En la implementación de Dispose() se escribiría el código de limpieza necesario, pues el significado de using consiste en que al acabar la ejecución de <instrucciones>, se llama automáticamente al método Dispose() de los objetos definidos en <declaraciones>

Hay que tener en cuenta que la llamada a Dispose() se hace sea cual sea la razón de que se deje de ejecutar las <instrucciones> Es decir, tanto si se ha producido una excepción como si se ha acabado su ejecución normalmente o con una instrucción de salto, Dispose() es siempre llamado. En realidad una instrucción using como:

using (R1 r1 = new R1())
{
  r1.F();
}

Es tratada por el compilador como:

{
  R1 r1 = new R1()
  try
  {
    r1.F();
  }
  finally
  {
    if (r1!=null)
      ((IDisposable) r1).Dispose();
  }
}

Si se declarasen varios objetos en <declaraciones>, a Dispose() se le llamaría en el orden inverso a como fueron declarados. Lo mismo ocurre si se anidasen varias instrucciones using: primero se llamaría al Dispose() de las variables declaradas en los using internos y luego a las de los externos. Así, estas dos instrucciones son equivalentes:

using (Recurso obj = new Recurso(), obj2= new Recurso())
{
  r1.F();
  r2.F();
}

using (Recurso obj = new Recurso())
{
  using (Recurso obj2= new Recurso())
  {
    r1.F();
    r2.F();
  }
}

El siguiente ejemplo resume cómo funciona la sentencia using:

using System;

class A:IDisposable
{
  public void Dispose()
  {
    Console.WriteLine("Llamado a Dispose() de {0}", Nombre);
  }
  
  public A(string nombre)
  {
    Nombre = nombre;
  }
  
  string Nombre;
}

class Using
{
  public static void Main()
  {
    A objk = new A("objk");
    
    using (A obj1 = new A("obj1"), obj2 = new A("objy"))
    {
      Console.WriteLine("Dentro del using");
    }
    Console.WriteLine("Fuera del using");
  }
}

La salida por pantalla resultante de ejecutar este ejemplo será:

Dentro del using
Llamando a Dispose() de objy
Llamando a Dispose() de obj1
Fuera del using

Como se deduce de los mensajes de salida obtenidos, justo antes de salirse del using se llama a los métodos Dispose() de los objetos declarados en la sección <declaraciones> de dicha instrucción y en el mismo orden en que fueron declarados.

. Instrucción fixed

La instrucción fixed se utiliza para fijar objetos en memoria de modo que el recolector de basura no pueda moverlos durante la ejecución de un cierto bloque de instrucciones.

Esta instrucción sólo tiene sentido dentro de regiones de código inseguro, concepto que se trata en el Tema 18: Código inseguro, por lo que será allí es donde se explique a fondo cómo utilizarla. Aquí sólo diremos que su sintaxis de uso es:

fixed(<tipoPunteros> <declaracionesPunterosAFijar>)
  <instrucciones >
 
Patrocinados
 

Copyright © 1999-2006 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

Hospedaje web y servidores dedicados linux por Ferca Network