Cargando...

Manejo de excepciones en C# (try, catch, finally)

Aprende a manejar excepciones en C# usando bloques try, catch y finally para gestionar errores de forma segura con ejemplos.

En C#, el manejo de errores permite capturar y controlar situaciones inesperadas durante la ejecución del programa. Así se evita que el programa se bloquee de forma abrupta y se pueden mostrar mensajes adecuados o tomar acciones alternativas. El manejo de errores utiliza los bloques try, catch y finally.


Uso de try-catch

El bloque try contiene código que puede generar una excepción. Si ocurre un error, el bloque catch lo gestiona.


try
{
    int numero = int.Parse("abc"); // Conversión inválida
    Console.WriteLine("Número: " + numero);
}
catch (FormatException ex)
{
    Console.WriteLine("Error: formato de número inválido.");
}
// Salida:
Error: formato de número inválido.

Múltiples bloques catch

Se pueden usar múltiples catch para diferentes tipos de error. Así cada excepción puede manejarse de manera específica.


try
{
    int[] numeros = { 1, 2, 3 };
    Console.WriteLine(numeros[5]); // Índice fuera de rango
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Error: índice de arreglo fuera de rango.");
}
catch (Exception ex)
{
    Console.WriteLine("Error inesperado: " + ex.Message);
}

Advanced Error Handling: Exception Filters (when)

En C#, los filtros de excepción permiten agregar una condición a un bloque catch utilizando la palabra clave when. Esto hace posible manejar una excepción únicamente si se cumple una condición específica.

La principal ventaja de los filtros de excepción es que la condición se evalúa antes de entrar en el bloque catch. Si la condición devuelve false, el runtime omite ese bloque y continúa buscando otra cláusula catch que coincida.

Esto permite:

Ejemplo: Manejo de errores HTTP según el código de estado

En aplicaciones reales, las solicitudes HTTP suelen devolver diferentes códigos de estado. Utilizando filtros de excepción, podemos manejar ciertos códigos de manera específica mientras mantenemos el código estructurado y fácil de leer.


using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();

        try
        {
            HttpResponseMessage response =
                await client.GetAsync("https://api.example.com/users/1");

            response.EnsureSuccessStatusCode();

            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            Console.WriteLine("Recurso no encontrado (404).");
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            Console.WriteLine("Acceso no autorizado (401). Verifica tus credenciales.");
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine("Ocurrió un error HTTP general.");
            Console.WriteLine($"Código de estado: {ex.StatusCode}");
        }
    }
}
  

En este ejemplo:

  • La condición when se evalúa antes de entrar en el bloque catch.
  • Si la condición es false, el runtime omite esa cláusula catch.
  • Los filtros de excepción fueron introducidos en C# 6 y se utilizan ampliamente en el desarrollo backend moderno.

Rethrowing Exceptions: throw; vs throw ex;

Una excepción capturada dentro de un bloque catch puede necesitar ser relanzada. Sin embargo, throw; y throw ex; no se comportan de la misma manera.

La diferencia principal radica en si se preserva la stack trace. La stack trace muestra la cadena de llamadas de métodos que llevó al error y la línea exacta donde ocurrió originalmente.

Ejemplo


using System;

class Program
{
    static void Main()
    {
        try
        {
            MethodA();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error registrado.");

            // Uso correcto:
            throw;

            // Uso incorrecto:
            // throw ex;
        }
    }

    static void MethodA()
    {
        MethodB();
    }

    static void MethodB()
    {
        throw new InvalidOperationException("Ocurrió un error.");
    }
}
  

En este ejemplo, si se usa throw;, la excepción aparecerá en la stack trace como originada en MethodB.

Si en cambio se utiliza throw ex;, la excepción parecerá haberse originado en el bloque catch, y la cadena de llamadas original se perderá.

  • Si una excepción debe relanzarse, siempre se debe preferir throw;.
  • throw ex; dificulta la depuración porque rompe la stack trace original.
  • La palabra clave throw también puede utilizarse para lanzar excepciones personalizadas.

Custom Exceptions

En aplicaciones reales, no todos los errores deben representarse mediante tipos de excepción generales como Exception u otras excepciones integradas. Para expresar con mayor claridad las violaciones de reglas de negocio (business logic) y los problemas específicos del dominio, se recomienda crear clases de excepción personalizadas.

Este enfoque:

Ejemplo


// 1. Definición de excepciones personalizadas
public class ProjectNotFoundException : Exception
{
    public ProjectNotFoundException(string message) : base(message) { }
}

public class ProjectIsCanceledException : Exception
{
    public int ProjectId { get; }
    public string ProjectName { get; }

    public ProjectIsCanceledException(int projectId, string projectName)
        : base($"Project '{projectName}' (Id: {projectId}) is canceled and cannot be processed.")
    {
        ProjectId = projectId;
        ProjectName = projectName;
    }
}

// 2. Uso
public void ProcessProject(Project project)
{
    if (project == null)
    {
        throw new ProjectNotFoundException("El proyecto no está registrado en el sistema.");
    }

    if (project.Status == ProjectStatus.Canceled)
    {
        throw new ProjectIsCanceledException(project.Id, project.Name);
    }

    // ...
}
  

En este ejemplo, se lanza una ProjectNotFoundException cuando el proyecto no existe en el sistema.

Si el proyecto está cancelado, se lanza una ProjectIsCanceledException. Esta excepción no solo contiene un mensaje de error, sino también los valores ProjectId y ProjectName, lo que hace que el registro y el análisis de errores sean más significativos.

  • Las excepciones personalizadas suelen representar violaciones de reglas de negocio o problemas del dominio.
  • La convención de nombres generalmente sigue el patrón SomethingException.
  • Se pueden agregar propiedades adicionales para proporcionar más información contextual.

Bloque finally

El bloque finally siempre se ejecuta, ocurra o no un error. Normalmente se usa para cerrar archivos o liberar conexiones a bases de datos.


try
{
    Console.WriteLine("Abriendo archivo...");
    throw new Exception("¡Archivo no encontrado!");
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message);
}
finally
{
    Console.WriteLine("Cerrando archivo...");
}
// Salida:
Abriendo archivo...
Error: ¡Archivo no encontrado!
Cerrando archivo...

Lanzar excepciones personalizadas

Puede definir condiciones de error propias y lanzar excepciones con throw.


static void Dividir(int a, int b)
{
    if (b == 0)
        throw new DivideByZeroException("Error: ¡División por cero!");
    Console.WriteLine("Resultado: " + (a / b));
}

static void Main()
{
    try
    {
        Dividir(10, 0);
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Error: " + ex.Message);
    }
}
// Salida:
Error: ¡División por cero!

Resumen

  • try: Contiene el código que puede lanzar excepciones.
  • catch: Captura y maneja las excepciones cuando ocurren.
  • finally: Siempre se ejecuta, normalmente para tareas de limpieza.
  • Múltiples bloques catch permiten manejar distintos tipos de excepciones por separado.
  • Los filtros de excepción (when) permiten un manejo condicional de excepciones.
  • throw; conserva la stack trace original al relanzar una excepción.
  • throw ex; reinicia la stack trace y generalmente debe evitarse.
  • Las excepciones personalizadas representan errores específicos del dominio o reglas de negocio.


Artículos relacionados