C# 高级错误处理:从基础 try/catch 到工业级模式
\n\n错误处理不仅仅是捕获异常,更是设计可靠系统的艺术。在 C# (.NET) 中,开发者常常局限于 try/catch 结构,但该平台提供了更强大的机制。本文将深入探讨高级技术:从异常过滤器到重试模式以及错误上下文传递。
1. 异常过滤器:精确而非占位
\n\nC# 6.0 引入的异常过滤器 (Exception Filters) 允许在进入 catch 块时指定条件,而无需重新抛出异常。这比在块内使用 if 的经典方法要清晰得多。
try\n{\n // 可能抛出异常的代码\n var result = ProcessData(input);\n}\ncatch (CustomException ex) when (ex.ErrorCode == 1001)\n{\n // 仅处理错误代码为 1001 的错误\n Log.Error($"严重错误:{ex.Message}");\n throw; // 如果需要,继续向上抛出\n}\ncatch (CustomException ex) when (ex.IsRecoverable)\n{\n // 可恢复的错误 — 记录日志并继续\n Log.Warning($"可恢复错误:{ex.Message}");\n RetryOperation();\n}\n\n优点:
\n- \n
- 无需为了过滤而再次
throw。 \n - 提高可读性和性能(调用栈不会被破坏)。 \n
- 可以组合多个具有不同条件的
catch块。 \n
2. 重试模式 (Retry) 与 Polly
\n\n并非所有错误都是致命的。对于临时故障(网络问题、数据库锁定),请使用重试模式。C# 内置功能有限,因此推荐使用 Polly 库。
\n\nusing Polly;\nusing Polly.Retry;\n\n// 定义重试策略\nAsyncRetryPolicy retryPolicy = Policy\n .Handle<HttpRequestException>() // 仅处理 HTTP 错误\n .WaitAndRetryAsync(\n 3, // 重试次数\n retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 指数退避延迟\n onRetry: (exception, timeSpan, retryCount, context) =>\n {\n Log.Warning($"第 {retryCount} 次尝试失败。{timeSpan.Seconds} 秒后重试。");\n });\n\n// 应用策略\nawait retryPolicy.ExecuteAsync(async () =>\n{\n var response = await httpClient.GetAsync("https://api.example.com/data");\n response.EnsureSuccessStatusCode();\n});\n\n重要提示:不要使用无限重试。始终设置最大尝试次数,并使用 Circuit Breaker(断路器)来保护系统免受级联故障的影响。
\n\n3. 正确传递异常:throw vs throw ex
\n\n最常见的错误之一是丢失调用栈。对于调试而言,throw 和 throw ex 之间的区别至关重要。
try\n{\n // ...\n}\ncatch (Exception ex)\n{\n // 错误:丢失原始调用栈\n // throw ex; \n\n // 正确:保留调用栈\n throw;\n\n // 或者:添加上下文,同时不丢失原始信息\n // throw new ApplicationException("模块 X 中发生错误", ex);\n}\n\n如果需要添加上下文,请使用 ExceptionDispatchInfo(来自 System.Runtime.ExceptionServices 命名空间)以在线程间重新抛出时精确保留调用栈。
ExceptionDispatchInfo.Capture(exception).Throw(); // 保留原始调用栈\n\n4. 自定义异常与错误上下文
\n\n标准异常通常不包含业务上下文。创建带有额外属性的自定义异常类。
\n\npublic class PaymentProcessingException : Exception\n{\n public int OrderId { get; }\n public dec