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:
- Un manejo de errores más limpio y legible
- Un control más preciso sobre la lógica de las excepciones
- Reducir la necesidad de instrucciones
ifanidadas dentro de los bloquescatch
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:
- Si la API devuelve
404 Not Found, se muestra un mensaje específico. - Si la API devuelve
401 Unauthorized, se maneja por separado. - Otros errores relacionados con HTTP se gestionan en el bloque general
HttpRequestException.
- La condición
whense evalúa antes de entrar en el bloquecatch. - Si la condición es
false, el runtime omite esa cláusulacatch. - 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.
-
throw;Vuelve a lanzar la excepción conservando su origen original (el número de línea y el método donde se produjo el error). Durante la depuración, puedes ver exactamente dónde comenzó realmente la excepción. Este es el método correcto y recomendado. -
throw ex;Reinicia la stack trace en esa línea y hace que parezca que el error se originó dentro del bloquecatch. Como resultado, se pierde la verdadera fuente del error (por ejemplo, un método más profundo), lo que dificulta la depuración.
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
throwtambié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:
- Permite diferenciar con mayor claridad los distintos tipos de errores
- Ofrece un manejo más específico en los bloques
catch - Proporciona información contextual adicional para el registro y el análisis
- Mejora la legibilidad y el mantenimiento del código
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
catchpermiten 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
Bucles en C# (for, foreach, while, do-while)
Aprende a usar los bucles for, foreach, while y do-while en C# para gestionar acciones repetitivas con ejemplos prácticos.
Estructuras condicionales en C# (if, else, switch)
Estructuras de decisión en C#: aprende a usar if, else if, else y switch para ejecutar acciones diferentes según las condiciones.
Interop en C# (Uso de bibliotecas C/C++)
Aprende Interop en C# para trabajar con bibliotecas C/C++, incluyendo P/Invoke y código no administrado.
Métodos y uso de parámetros en C#
Aprende a definir métodos y usar parámetros en C#, incluyendo parámetros por valor y referencia, parámetros opcionales y ejemplos.
Principios SOLID en C#
Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.