Las excepciones en C# representan condiciones inesperadas que alteran el flujo normal de ejecución. Cuando ocurre una excepción, se genera un objeto que encapsula detalles del error, como la ubicación y la causa.
Estructura de Excepciones
Todas las excepciones en C# derivan de System.Exception, formando una jerarquía de clases. Las excepciones comunes incluyen:
System.Exception: Clase base con propiedades comoMessageyStackTrace.System.SystemException: Para errores del tiempo de ejecución, comoNullReferenceException,DivideByZeroExceptionoIndexOutOfRangeException.
Ejemplo de una excepción por división entre cero:
int numerator = 10;
int denominator = 0;
try
{
int quotient = numerator / denominator;
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error aritmético: {ex.Message}");
}
Mecanismo de Manejo de Excepciones
C# utiliza bloques try, catch, finally y throw para gestionar excepciones.
Bloque try-catch-finally
El bloque try contiene código propenso a errores. Los bloques catch capturan excepciones por tipo, y finally garantiza la ejecución de limpieza de recursos.
FileStream stream = null;
string path = "data.txt";
try
{
stream = new FileStream(path, FileMode.Open);
Console.WriteLine("Archivo abierto.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Archivo no encontrado: {ex.FileName}");
}
finally
{
stream?.Close();
Console.WriteLine("Recurso liberado.");
}
Bloques catch Múltiples
Se pueden encadenar varios catch, colocando excepciones específicas antes de las genéricas para un manejo preciso.
int[] values = new int[3];
int divisor = 5;
try
{
int result = values[4] / divisor;
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine($"Índice fuera de rango: {ex.Message}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"División inválida: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Error general: {ex.Message}");
}
Lanzamiento Manual de Excepciones
Se puede usar throw para generar excepciones intencionales, manteniendo la traza de pila con throw en lugar de throw ex.
void ValidateAge(int age)
{
if (age < 0)
throw new ArgumentException("La edad no puede ser negativa.", nameof(age));
}
try
{
ValidateAge(-3);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Parámetro inválido: {ex.Message}");
}
Excepciones Personalizadas
Para errores específicos de negocio, se pueden crear clases de excepción que hereden de Exception, implementando constructores estándar.
public class InvalidOrderException : Exception
{
public InvalidOrderException() { }
public InvalidOrderException(string message) : base(message) { }
public InvalidOrderException(string message, Exception inner) : base(message, inner) { }
}
Ejemplo de uso en una validación de pedido:
void ProcessOrder(int orderId)
{
if (orderId <= 0)
throw new InvalidOrderException($"ID de pedido inválido: {orderId}");
}
try
{
ProcessOrder(-1);
}
catch (InvalidOrderException ex)
{
Console.WriteLine($"Error en pedido: {ex.Message}");
}
Cadenas de Excepciones
La propiedad InnerException permite encadenar excepciones, preservando el contexto original. Esto facilita el diagnóstico al envolver excepciones de bajo nivel.
void OuterOperation()
{
try
{
InnerOperation();
}
catch (Exception innerEx)
{
throw new InvalidOperationException("Fallo en operación externa.", innerEx);
}
}
void InnerOperation()
{
throw new IOException("Error de lectura en disco.");
}
try
{
OuterOperation();
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Excepción externa: {ex.Message}");
Exception current = ex.InnerException;
int depth = 1;
while (current != null)
{
Console.WriteLine($"Excepción interna {depth}: {current.Message}");
current = current.InnerException;
depth++;
}
}
Sentencia using para Gestión de Recursos
La sentencia using simplifica la liberación de recursos que implementan IDisposable, garantizando que se llame a Dispose.
using (var reader = new StreamReader("file.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
} // Llama automáticamente a Dispose() al salir del bloque
C# 8.0 introduce una sintaxis simplificada:
using var writer = new StreamWriter("output.txt");
writer.WriteLine("Datos guardados.");
// Dispose() se invoca al finalizar el ámbito
Este enfoque evita fugas de recursos como conexiones de archivos o bases de datos.