Excepciones en C#: Manejo, Personalización y Recursos

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 como Message y StackTrace.
  • System.SystemException: Para errores del tiempo de ejecución, como NullReferenceException, DivideByZeroException o IndexOutOfRangeException.

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.

Etiquetas: CSharp excepciones ManejoDeErrores .NET ExcepcionesPersonalizadas

Publicado el 6-10 19:10