1. Introducción
Cuando tu programa se vuelve un poco más complejo que un simple Hello, world!, enseguida te topas con el reto de manejar errores de distintos tipos de formas diferentes. Imagina: trabajas con archivos, red, base de datos — y para cada escenario de error la reacción debe ser distinta. Por ejemplo, si falta un archivo puedes sugerir al usuario elegir otro, y si hay un error de red — repetir la operación o mostrar un mensaje animando tipo "Revisa el cable, a ver si el gato lo ha mordido otra vez".
En C# para esto se usan varios bloques catch seguidos. Cada bloque se especializa en su propio tipo de excepción (y sus herederos).
Estructura de múltiples catch
try
{
// Aquí va el código que puede lanzar diferentes excepciones
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Archivo no encontrado: {ex.Message}");
}
catch (IOException ex) // captura todos los errores de entrada/salida, excepto FileNotFoundException
{
Console.WriteLine($"Error de entrada/salida: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Algo salió mal: {ex.Message}");
}
¡Importante! El compilador recorre los bloques de arriba abajo y elige el primero que encaje por tipo. Si se captura un FileNotFoundException, ya no sigue buscando más abajo.
Ilustración de la "cadena"
| Tipo de excepción | ¿Qué bloque la captura? |
|---|---|
| FileNotFoundException | El primer catch |
| IOException (otro) | El segundo catch |
| ArgumentException | El tercero (general) catch |
¿Por qué no mezclar?
La trampa más "amplia" — por ejemplo, con catch (Exception) — siempre ponla al final, si no, "tragará" todas las excepciones antes de tiempo y los catch especializados nunca se ejecutarán.
Es como apagar todas las alarmas de incendio del edificio solo porque una saltó por una tostadora quemada: así los siguientes incendios pasarán desapercibidos para el sistema.
Ejemplo práctico
using System;
using System.IO;
class Program
{
static void Main()
{
try
{
double result = CalculateAverageAgeFromFile("users.txt");
Console.WriteLine($"Edad media — {result}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Error: archivo no encontrado. Revisa la ruta.");
}
catch (FormatException ex)
{
Console.WriteLine("Error de datos: no se puede leer la edad.");
}
catch (Exception ex)
{
Console.WriteLine($"Otro error: {ex.Message}");
}
}
static double CalculateAverageAgeFromFile(string filePath)
{
// (Implementación: lee el archivo, parsea edades, calcula la media)
// ...
throw new NotImplementedException();
}
}
Aquí separamos claramente qué hacer si falta el archivo (FileNotFoundException), y qué si los datos del archivo están "mal" (FormatException). Todos los demás casos los pilla el tercer bloque "de reserva".
2. Filtros catch: capturando matices
Los múltiples bloques catch son útiles, pero a veces no basta. Puede que dentro de un mismo tipo de excepción quieras reaccionar de forma distinta, según las circunstancias.
Por ejemplo, si la red no funciona porque se acabó el internet — podemos intentar reconectar. Pero si el servidor simplemente no responde — quizá hay que mostrar otro mensaje.
Aquí entran en juego los filtros catch — una supercaracterística de C# que te permite capturar no solo por tipo, sino también por una condición adicional.
Sintaxis del filtro when
catch (IOException ex) when (ex.Message.Contains("no hay disco"))
{
Console.WriteLine("¡Ups! Parece que quitaste el pendrive.");
}
catch (IOException ex)
{
Console.WriteLine("Otro error de entrada/salida: " + ex.Message);
}
Aquí el primer bloque solo captura aquellos IOException cuyo mensaje contiene la frase "no hay disco" — todo lo demás va al segundo bloque.
Aplicación en la vida real
Los filtros son especialmente útiles al trabajar con errores de red, cuando según las propiedades internas de la excepción decides si reintentar o simplemente informar del error.
Otro ejemplo: imagina que en un método que parsea un archivo, no quieres solo un FormatException "genérico", sino distinguir si el problema es con la edad (por ejemplo, si la edad está escrita como "abc" en vez de un número).
catch (FormatException ex) when (ex.Message.Contains("edad"))
{
Console.WriteLine("Error: no se pudo leer la edad. Revisa los datos.");
}
catch (FormatException ex)
{
Console.WriteLine("Error de formato de datos: " + ex.Message);
}
Filtros y rendimiento
Los filtros son muy cómodos, pero recuerda que la condición del filtro se evalúa antes de entrar al bloque catch, o sea, si no se cumple — el cuerpo del bloque ni se toca.
Por cierto, si el filtro lanza una excepción por sí mismo (por ejemplo, si en tu expresión en when ocurre una división por cero) — esa excepción catch nunca la capturará. Hay que tener cuidado con eso.
3. Combinando múltiples catch y filtros
Imagina que en tu proyecto necesitas manejar no solo el tipo de excepción, sino también su estado interno. Por ejemplo, al trabajar con IOException quieres comportamientos distintos si el error del sistema de archivos es por permisos o por falta de espacio en disco.
try
{
File.AppendAllText("log.txt", "Nuevo registro\n");
}
catch (IOException ex) when (ex.Message.Contains("Sin espacio"))
{
Console.WriteLine("Error: ¡No queda espacio en el disco!");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Error: no tienes permisos para escribir el archivo. Ejecuta como administrador.");
}
catch (IOException ex)
{
Console.WriteLine("Otro error de disco: " + ex.Message);
}
Aquí usamos tanto filtro como separación por tipos de error. Esta flexibilidad es especialmente valiosa al desarrollar una app compleja, donde es importante reaccionar de forma informativa ante fallos frecuentes.
4. Particularidades y "trampas" de los filtros y múltiples catch
- El filtro catch puede usar la variable de excepción (ex), pero no puede modificarla.
- Si lanzas una excepción dentro de when, no será "capturada" por ese mismo catch — subirá por la pila como si el filtro no existiera.
- La lógica del filtro pasa a ser parte del contrato: tu compi de proyecto debe saber que no todo IOException será capturado — solo si se cumple la condición.
- Si usas solo un catch para todo el método, pero dentro pones un filtro, puede que algunas excepciones "de más" no las captures nunca. Así que si tienes dudas — usa bloques explícitos y separados.
- En apps grandes puedes usar filtros para lógica centralizada, por ejemplo, loguear solo errores críticos.
5. Escenarios para entrevistas y trabajo real
Saber de filtros y múltiples catch no es solo para lucirse o sacar nota en clean code. Es una habilidad real que te pueden pedir en entrevistas, sobre todo si el puesto implica mantener una app grande, compleja y distribuida.
Ejemplos de preguntas:
- ¿Cómo manejarías en C# distintos escenarios de error al leer un archivo, para mostrar mensajes diferentes según la situación (archivo no existe, disco ocupado, datos corruptos)?
- ¿Cómo evitar "tapar" un error importante si pones un bloque general catch (Exception)?
- ¿Para qué sirve el filtro catch? ¿Puede haber un throw dentro?
GO TO FULL VERSION