Uso de excepciones
En C#, los errores del programa en tiempo de ejecución se propagan a través del programa mediante un mecanismo denominado excepciones. Las excepciones las inicia el código que encuentra un error y las detecta el código que puede corregir dicho error. El entorno de ejecución .NET o el código de un programa pueden producir excepciones. Una vez iniciada, una excepción se propaga hasta la pila de llamadas hasta que encuentra una instrucción catch
para la excepción. Las excepciones no detectadas se controlan mediante un controlador de excepciones que ofrece el sistema y muestra un cuadro de diálogo.
Las excepciones están representadas por clases derivadas de Exception. Esta clase identifica el tipo de excepción y contiene propiedades que tienen los detalles sobre la excepción. Iniciar una excepción implica crear una instancia de una clase derivada de excepción, configurar opcionalmente las propiedades de la excepción y luego producir el objeto con la palabra clave throw
. Por ejemplo:
class CustomException : Exception
{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}
Cuando se inicia una excepción, el entorno runtime comprueba la instrucción actual para ver si se encuentra dentro de un bloque try
. Si es así, se comprueban los bloques catch
asociados al bloque try
para ver si pueden detectar la excepción. Los bloques Catch
suelen especificar tipos de excepción; si el tipo del bloque catch
es el mismo de la excepción, o una clase base de la excepción, el bloque catch
puede controlar el método. Por ejemplo:
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
Si la instrucción que inicia una excepción no está en un bloque try
, o si el bloque try
que la encierra no tiene un elemento catch
coincidente, el entorno de ejecución busca una instrucción try
y bloques catch
en el método de llamada. El entorno runtime sigue hasta la pila de llamadas para buscar un bloque catch
compatible. Después de encontrar el bloque catch
y ejecutarlo, el control pasa a la siguiente instrucción después de dicho bloque catch
.
Una instrucción try
puede contener más de un bloque catch
. Se ejecuta la primera instrucción catch
que pueda controlar la excepción; las instrucciones catch
siguientes se omiten, aunque sean compatibles. Ordene los bloques catch de más específicos (o más derivados) a menos específicos. Por ejemplo:
using System;
using System.IO;
namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}
Para que el bloque catch
se ejecute, el entorno runtime busca bloques finally
. Los bloques Finally
permiten al programador limpiar cualquier estado ambiguo que pudiera haber quedado tras la anulación de un bloque try
o liberar los recursos externos (como identificadores de gráficos, conexiones de base de datos o flujos de archivos) sin tener que esperar a que el recolector de elementos no utilizados en el entorno de ejecución finalice los objetos. Por ejemplo:
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
Si WriteByte()
ha iniciado una excepción, el código del segundo bloque try
que intente reabrir el archivo generaría un error si no se llama a file.Close()
, y el archivo permanecería bloqueado. Como los bloques finally
se ejecutan aunque se inicie una excepción, el bloque finally
del ejemplo anterior permite que el archivo se cierre correctamente y ayuda a evitar un error.
Si no se encuentra ningún bloque catch
compatible en la pila de llamadas después de iniciar una excepción, sucede una de estas tres acciones:
- Si la excepción se encuentra en un finalizador, este se anula y, si procede, se llama al finalizador base.
- Si la pila de llamadas contiene un constructor estático o un inicializador de campo estático, se inicia una excepción TypeInitializationException, y la excepción original se asigna a la propiedad InnerException de la nueva excepción.
- Si se llega al comienzo del subproceso, este finaliza.