Utilizar excepciones (Guía de programación de C#)

Actualización: noviembre 2007

En el lenguaje C#, los errores del programa se difunden en tiempo de ejecución a través del programa mediante un mecanismo denominado excepciones. Las excepciones se producen cuando el código encuentra un error y se detectan mediante el código que puede corregir el error. Las excepciones se pueden producir mediante el Common Language Runtime (CLR) de .NET Framework o mediante código de un programa. Una vez que se produce una excepción, ésta se difunde a la pila de llamadas hasta que se encuentra una instrucción catch para la excepción. Las excepciones no detectadas se identifican a través de un controlador de excepciones genérico proporcionado por el sistema que muestra un cuadro de diálogo.

Las clases derivadas de Exception representan estas excepciones. Esta clase identifica el tipo de excepción y contiene las propiedades que albergan detalles acerca de ésta. Producir una excepción implica crear una instancia de una clase derivada de la excepción, configurar opcionalmente las propiedades de la excepción y, a continuación, iniciar el objeto con la palabra clave throw. Por ejemplo:

class CustomException : Exception
{
    public CustomException(string message)
    {

    }

}
private static void TestThrow()
{
    CustomException ex =
        new CustomException("Custom exception in TestThrow()");

    throw ex;
}

Después de que se produzca una excepción, el motor en tiempo de ejecución comprueba la instrucción actual para ver si está dentro de un bloque try. Si es así, se comprueba cualquier bloque catch asociado al bloque try para ver si puede detectar la excepción. Los bloques Catch especifican generalmente los tipos de excepción; si el tipo del bloque catch es el mismo tipo de la excepción o de una clase base de la excepción, el bloque catch puede controlar el método. Por ejemplo:

static void TestCatch()
{
    try
    {
        TestThrow();
    }
    catch (CustomException ex)
    {
        System.Console.WriteLine(ex.ToString());
    }
}

Si la instrucción que produce una excepción no está dentro de un bloque try o el bloque try que la encierra no tiene un bloque catch coincidente, el motor en tiempo de ejecución comprueba el método de llamada a una instrucción try y a los bloques catch. El motor en ejecución continúa hasta la pila de llamadas, en búsqueda de un bloque catch compatible. Después de encontrar y ejecutar el bloque catch, el control se pasa a la siguiente instrucción después de ese bloque catch.

Una instrucción try puede contener más de un bloque catch. Se ejecuta la primera instrucción catch que puede controlar la excepción; cualquier instrucción catch posterior, aun cuando sea compatible, se omite. Por consiguiente, los bloques catch siempre deberían ordenarse de más específico (o más derivado) a menos específico. Por ejemplo:

static void TestCatch2()
{
    System.IO.StreamWriter sw = null;
    try
    {
        sw = new System.IO.StreamWriter(@"C:\test\test.txt");
        sw.WriteLine("Hello");
    }

    catch (System.IO.FileNotFoundException ex)
    {
        System.Console.WriteLine(ex.ToString());  // put the more specific exception first
    }

    catch (System.IO.IOException ex)
    {
        System.Console.WriteLine(ex.ToString());  // put the less specific exceptions last
    }
    finally 
    {
        sw.Close();
    }

    System.Console.WriteLine("Done");  // this statement is executed after the catch block
}

Antes de que se ejecute el bloque catch, el motor en tiempo de ejecución comprueba los bloques finally. Los bloques Finally permiten al programador limpiar cualquier estado ambiguo que pudiera haber quedado de un bloque try anulado o liberar cualquier recurso externo (como controladores de gráficos, conexiones de base de datos o secuencias de archivos) sin esperar a que el recolector de elementos no utilizados del motor en tiempo de ejecución finalice los objetos. Por ejemplo:

static void TestFinally()
{
    System.IO.FileStream file = null;
    //Change the path to something that works on your machine
    System.IO.FileInfo fileInfo = new System.IO.FileInfo(@"C:\file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
        if (file != null)
        {
            file.Close();
        }
    }

    try
    {
        file = fileInfo.OpenWrite();
        System.Console.WriteLine("OpenWrite() succeeded");
    }
    catch (System.IO.IOException)
    {
        System.Console.WriteLine("OpenWrite() failed");
    }
}

Si WriteByte() produjo una excepción, el código del segundo bloque try que intenta abrir de nuevo el archivo produciría un error si no se llama a file.Close(), y el archivo permanecería bloqueado. Debido a que los bloques finally se ejecutan aunque se produzca una excepción, el bloque finally del ejemplo anterior permite al archivo cerrarse correctamente y ayuda a evitar un error.

Si no se encuentra ningún bloque catch compatible en la pila de llamadas después de que se produzca una excepción, puede suceder una de estas tres cosas:

  • Si la excepción está dentro de un destructor, el destructor se anula y se llama al destructor base, si existe.

  • Si la pila de llamadas contiene un constructor estático o un inicializador de campo estático, se produce una TypeInitializationException, con la excepción original asignada a la propiedad InnerException de la nueva excepción.

  • Si se alcanza el comienzo del subproceso, éste finaliza.

Vea también

Conceptos

Guía de programación de C#

Referencia

Excepciones y control de excepciones (Guía de programación de C#)