Manejo avanzado de errores en C#: desde try/catch básico hasta patrones industriales
\n\nEl manejo de errores no es solo atrapar excepciones, sino el arte de diseñar sistemas robustos. En C# (.NET), los desarrolladores a menudo se limitan a la construcción try/catch, pero la plataforma ofrece mecanismos mucho más potentes. En este artículo, analizaremos técnicas avanzadas: desde filtros de excepciones hasta patrones de reintento y transmisión de contexto de error.
1. Filtros de excepciones: precisión en lugar de marcadores de posición
\n\nLos filtros de excepciones (Exception Filters), introducidos en C# 6.0, permiten especificar una condición para entrar en el bloque catch sin volver a lanzar la excepción. Esto es mucho más limpio que el enfoque clásico con if dentro del bloque.
try\n{\n // Código que puede lanzar una excepción\n var result = ProcessData(input);\n}\ncatch (CustomException ex) when (ex.ErrorCode == 1001)\n{\n // Solo manejamos errores con código 1001\n Log.Error($"Error crítico: {ex.Message}");\n throw; // Relanzamos si es necesario\n}\ncatch (CustomException ex) when (ex.IsRecoverable)\n{\n // Error recuperable — registramos y continuamos\n Log.Warning($"Error recuperable: {ex.Message}");\n RetryOperation();\n}\n\nVentajas:
\n- \n
- No se requiere un
throwadicional para filtrar. \n - Mejora la legibilidad y el rendimiento (la pila de llamadas no se daña). \n
- Se pueden combinar múltiples bloques
catchcon diferentes condiciones. \n
2. Patrón de "Reintento" (Retry) y Polly
\n\nNo todos los errores son fatales. Para fallos temporales (problemas de red, bloqueos de BD), use el patrón de reintentos. Las capacidades integradas de C# son limitadas, por lo que se recomienda la biblioteca Polly.
\n\nusing Polly;\nusing Polly.Retry;\n\n// Definimos la política de reintentos\nAsyncRetryPolicy retryPolicy = Policy\n .Handle<HttpRequestException>() // Solo manejamos errores HTTP\n .WaitAndRetryAsync(\n 3, // Número de intentos\n retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // Retardo exponencial\n onRetry: (exception, timeSpan, retryCount, context) =>\n {\n Log.Warning($"Intento {retryCount} fallido. Reintentando en {timeSpan.Seconds} seg.");\n });\n\n// Aplicamos la política\nawait retryPolicy.ExecuteAsync(async () =>\n{\n var response = await httpClient.GetAsync("https://api.example.com/data");\n response.EnsureSuccessStatusCode();\n});\n\nImportante: No use reintentos infinitos. Siempre establezca un número máximo de intentos y use Circuit Breaker para proteger el sistema de fallos en cascada.
\n\n3. Transmisión correcta de excepciones: throw vs throw ex
\n\nUno de los errores más comunes es la pérdida de la pila de llamadas. La diferencia entre throw y throw ex es crítica para la depuración.
try\n{\n // ...\n}\ncatch (Exception ex)\n{\n // INCORRECTO: perdemos la pila original\n // throw ex; \n\n // CORRECTO: conservamos la pila\n throw;\n\n // O: agregamos contexto sin perder el original\n // throw new ApplicationException("Error en el módulo X", ex);\n}\n\nSi necesita agregar contexto, use ExceptionDispatchInfo (del espacio de nombres System.Runtime.ExceptionServices) para conservar con precisión la pila al relanzar entre hilos.
ExceptionDispatchInfo.Capture(exception).Throw(); // Conserva la pila original\n\n4. Excepciones personalizadas y contexto de error
\n\nLas excepciones estándar a menudo no contienen contexto de negocio. Cree sus propias clases de excepción con propiedades adicionales.
\n\npublic class PaymentProcessingException : Exception\n{\n public int OrderId { get; }\n public dec