Excepciones.

Durante la fase de depuración de un programa te encontrarás con multitud de errores que deberás corregir antes de su acabado final. E incluso cuando el programa ha sido depurado en su totalidad, habrá situaciones esperadas derivadas de un incorrecto uso del usuario o de un comportamiento peculiar del sistema operativo, que irremediablemente darán como resultado errores que no habías detectado en la fase de depuración. Evidentemente, todos estos posibles errores deben ser controlados para que la aplicación no "explote". C# permite el control de errores, también llamados excepciones, que serán tratadas convenientemente en un contexto más apropiado para ellas fuera del flujo normal de ejecución del programa. A través del uso de control de excepciones, se nos ofrece la posibilidad limpia de verificación y control de errores. Ten en cuenta que el control de excepciones es un tema de suma importancia para la construcción de aplicaciones bien depuradas y a prueba de todo tipo de fallos.

Habrás pensado que, en el momento de realizar una operación determinada, como por ejemplo la escritura en un archivo en el disco, puedes manejar los errores con unos cuantos 'if'. Por ejemplo, añadir tantos 'if' como posibles errores puedan ocurrir: el fichero no existe, la ruta del archivo es errónea o está incompleta, el fichero es de sólo lectura, etc. Pero es posible que se produzca un error que tú no tenías pensado, como por ejemplo que no tienes permisos de escritura en una ruta determinada porque es un archivo que se encuentra en una ubicación de red. ¿Qué pasaría entonces? Pues muy sencillo: la aplicación explotará y tú quedarás como un mal programador.

En la sección en la que estudiamos las sentencias de control vimos el uso de los bloques 'try-catch'. Es un buen método de control de errores:

try
{
    // Tarea a realizar.
}
catch (ClaseDeExcepción exception1)
{
    // Código para tratar la excepción.
}
catch (OtraClaseDeExcepción exception2)
{
    // Código para tratar la otra excepción.
}
finally
{
    // Código si la tarea no se ha podido realizar de ninguna manera.
}

Lo anterior está muy bien, y ciertamente la mayoría de las ocasiones solventarás posibles exceptiones no controladas a través de bloques 'try-catch' de una manera óptima. Pero aún así, es posible hacer un tratamiento de control de excepciones no controladas de una manera mucho más elegante.

Las excepciones son objetos derivados de la clase 'Exception' del espacio de nombres 'System'. Así, por ejemplo, cuando ocurre una excepción del tipo 'ArithmeticException', automáticamente C# crea un objeto de esta clase. La clase 'Exception' cubre todas las excepciones de una aplicación normal, y de ella derivan las clases 'SystemException' y 'ApplicationException', las cuales cubren las excepciones propias de programación y las excepciones lanzadas por un program del usuario (y no por el motor de ejecución de .NET), respectivamente. Vamos con un ejemplo:

Vamos con un ejemplo. Prueba a compilar este código fuente, y verás que la clase 'OutOfRangeException' tiene sus propios miembros públicos, como la propiedad 'Message', que hemos utilizado para leer su valor y mostrar el mensaje de error en la salida de la consola.

using System;
public class Program
{
    public static void Main()
    {
        int [] numbers = new int[4];
        try
        {
            Console.WriteLine("Antes de que se genere la excepción.");
            // Se genera la excepción de índice fuera de los límites de la matriz.
            for (int i = 0; i < 10; i++)
            {
                numbers[i] = i;
                Console.WriteLine("numbers[{0}]: {1}", i, numbers[i]);
            }
        }
        catch (IndexOutOfRangeException exception)
        {
            Console.WriteLine(exeption.Message);
        }
        Console.WriteLine("Fin del tratamiento de la excepción.");
    }
}

En la siguiente table se muestran las clases de excepciones más comunes y su funcionalidad:

EXCEPCIÓN FUNCIONALIDAD
'DivideByZeroException' Se intenta dividir entre cero.
'IndexOutOfRangeException' Índice fuera de los límites de la matriz.
'InvalidCastException' El molde del tiempo de ejecución no es válido.
'OutOfMemoryException' Al llamar al operador 'new', no hay sufuciente espacio libre en memoria.
'OverflowException' Desbordamiento aritmético.
'NullReferenceException' Se ha intentado operar con una referencia 'null', y la referencia no hace referencia a un objeto.
'StackOverflowException' Se ha sobrepasado la capacidad de la pila.

La clase 'Exception' posee propiedades que te ayudarán a la depuración de errores mediante la información que nos proporcionan. Hasta ahora hemos utilizado la propiedad 'Message', pero hay otras:

PROPIEDAD FUNCIONALIDAD
Data Colección de pares clave-valor que proporcionan información adicional acerca de la excepción.
HelpLink Vínculo al fichero de ayuda de la excepción.
InnerException Obtiene el objeto de la clase 'Exception' que se ha creado en la excepción.
Source Nombre de la aplicación o del objeto que generó la excepción.
StackTrace Estado de la pila de llamadas en el momento de la excepción.
TargetSite Método que produce la excepción.

Como ya en secciones anteriores del curso estudiamos los bloques 'try-catch', supongo que lo visto hasta ahora en cuanto a excepciones no te ha aportado muchos conceptos nuevos. Es por eso que nos queda estudiar la manera de crear nuestras propias excepciones. Lo haremos mediante la palabra clave 'throw'. Fíjate en el siguiente ejemplo basado en el anterior:

using System;
public class Program
{
    public static void Main()
    {
        int [] numbers = new int[4];
        try
        {
            Console.WriteLine("Antes de que se genere la excepción.");
            // Se genera la excepción de índice fuera de los límites de la matriz.
            for (int i = 0; i < 10; i++)
            {
                if (i == numbers.Length)
                    throw new MyException();
                numbers[i] = i;
                Console.WriteLine("numbers[{0}]: {1}", i, numbers[i]);
            }
        }
        catch (IndexOutOfRangeException exception1)
        {
            Console.WriteLine(exeption1.Message);
        }
        catch (MyException exception2)
        {
            Console.WriteLine(exeption2.Message);
        }
        Console.WriteLine("Fin del tratamiento de la excepción.");
    }
}
public class MyException : Exception
{
    // Constructor, atributos, propiedades y métodos que deseas implamentar para responder a la excepción propia creada.
}

Como ves, tu nueva clase de excepción 'MyException' deriva de la clase 'Exception' de .NET Framework, y ello es así para poder tener al alcance la funcionalidad de esa clase y así ahorrarte líneas de código fuente, tal como vimos en la sección en la que estudiamos la herencia. Queda para tu imaginación qué tipo de nuevas excepciones necesitarás en tus programas y la manera de implementar tus clases de excepciones. Las posibilidades son infinitas.