实战派C#异常处理5个真实项目场景与避坑指南在C#开发中异常处理是每个开发者必须掌握的核心技能。但传统的try-catch-finally语法讲解往往停留在表面难以应对实际项目中的复杂场景。本文将带你通过5个真实项目案例深入理解异常处理的精髓。1. 文件异步读写中的资源泄漏陷阱在文件操作中异常处理不当可能导致资源泄漏。考虑以下常见但存在问题的代码public async Taskstring ReadFileAsync(string path) { try { using var reader new StreamReader(path); return await reader.ReadToEndAsync(); } catch (IOException ex) { Console.WriteLine($读取文件出错: {ex.Message}); return string.Empty; } }这段代码看似合理实则存在潜在问题资源释放时机不明确using语句会在StreamReader超出作用域时释放资源但在异步操作中异常可能发生在await之后异常信息不完整仅捕获IOException忽略了其他可能的异常类型日志记录不足仅输出到控制台缺乏结构化日志改进方案public async Taskstring ReadFileSafelyAsync(string path) { FileStream? fileStream null; StreamReader? reader null; try { fileStream new FileStream(path, FileMode.Open); reader new StreamReader(fileStream); var content await reader.ReadToEndAsync(); return content; } catch (FileNotFoundException ex) { _logger.LogError(ex, 文件未找到: {Path}, path); throw new CustomFileException($文件未找到: {path}, ex); } catch (UnauthorizedAccessException ex) { _logger.LogError(ex, 无权限访问文件: {Path}, path); throw new CustomFileException($无权限访问: {path}, ex); } catch (IOException ex) { _logger.LogError(ex, IO异常: {Path}, path); throw new CustomFileException($文件读取失败: {path}, ex); } finally { reader?.Dispose(); fileStream?.Dispose(); } }关键改进点显式资源管理不再依赖using语句而是手动管理FileStream和StreamReader的生命周期精细化异常捕获区分不同类型的IO异常提供更有针对性的处理结构化日志记录使用ILogger接口记录完整异常信息自定义异常类型封装原始异常提供更有业务意义的错误信息2. 数据库连接池管理中的异常处理数据库操作是异常高发区特别是连接池管理。看一个典型的问题案例public void UpdateUserProfile(User user) { using var connection new SqlConnection(_connectionString); var command new SqlCommand(UPDATE Users SET NameName WHERE IdId, connection); try { connection.Open(); command.Parameters.AddWithValue(Name, user.Name); command.Parameters.AddWithValue(Id, user.Id); command.ExecuteNonQuery(); } catch (SqlException ex) { Console.WriteLine($数据库错误: {ex.Message}); } }这段代码的问题包括连接泄漏风险虽然使用了using但在异常发生时可能无法正确关闭连接参数注入漏洞使用AddWithValue可能导致参数类型推断错误事务缺失缺乏事务支持操作不原子重试机制缺失对临时性错误没有重试逻辑优化后的实现public async Task UpdateUserProfileSafelyAsync(User user) { await Policy .HandleSqlException(ex ex.IsTransient) .WaitAndRetryAsync(3, retryAttempt TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))) .ExecuteAsync(async () { await using var connection new SqlConnection(_connectionString); await using var transaction await connection.BeginTransactionAsync(); try { await connection.OpenAsync(); var command new SqlCommand( UPDATE Users SET NameName WHERE IdId, connection, transaction); command.Parameters.Add(Name, SqlDbType.NVarChar, 100).Value user.Name; command.Parameters.Add(Id, SqlDbType.Int).Value user.Id; await command.ExecuteNonQueryAsync(); await transaction.CommitAsync(); } catch (SqlException ex) when (ex.Number 1205) // 死锁 { await transaction.RollbackAsync(); _logger.LogWarning(ex, 更新用户时发生死锁将重试); throw; } catch (SqlException ex) { await transaction.RollbackAsync(); _logger.LogError(ex, 更新用户失败); throw new DataAccessException(用户更新失败, ex); } }); }改进亮点异步支持全面采用async/await模式重试策略使用Polly库实现指数退避重试事务安全明确的事务管理确保操作原子性参数安全指定参数类型和长度防止SQL注入死锁处理特殊处理死锁错误(1205)自动重试3. Web API调用中的异常传播调用外部API时异常处理尤为关键。以下是一个常见的反模式public async TaskWeatherData GetWeatherAsync(string city) { try { var response await _httpClient.GetAsync($weather/{city}); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsAsyncWeatherData(); } catch (HttpRequestException) { return new WeatherData(); // 返回空对象 } }问题分析异常吞噬捕获异常后简单地返回空对象调用方无法感知错误重试缺失对网络波动等临时错误没有重试机制超时未配置使用默认超时可能导致长时间阻塞没有断路器当API持续失败时没有熔断机制健壮的API调用实现public async TaskWeatherData GetWeatherResilientlyAsync(string city) { var policy PolicyWeatherData .HandleHttpRequestException() .OrResult(r r null) .CircuitBreakerAsync( exceptionsAllowedBeforeBreaking: 3, durationOfBreak: TimeSpan.FromMinutes(1), onBreak: (ex, breakDelay) _logger.LogWarning($断路器开启{breakDelay.TotalSeconds}秒内不再尝试), onReset: () _logger.LogInformation(断路器重置)) .WrapAsync(Policy .HandleHttpRequestException() .WaitAndRetryAsync(3, retryAttempt TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (ex, delay) _logger.LogWarning($请求失败{delay.TotalSeconds}秒后重试: {ex.Message}))); return await policy.ExecuteAsync(async () { using var cts new CancellationTokenSource(TimeSpan.FromSeconds(30)); try { var response await _httpClient.GetAsync($weather/{city}, cts.Token); if (response.StatusCode HttpStatusCode.NotFound) throw new WeatherServiceException($城市{city}不存在); response.EnsureSuccessStatusCode(); var data await response.Content.ReadAsAsyncWeatherData(); return data ?? throw new WeatherServiceException(返回数据为空); } catch (TaskCanceledException ex) when (!cts.Token.IsCancellationRequested) { throw new WeatherServiceException(请求超时, ex); } catch (HttpRequestException ex) { throw new WeatherServiceException(天气服务不可用, ex); } }); }关键增强断路器模式当连续失败时自动熔断避免雪崩效应重试机制对临时性错误自动重试超时控制显式设置30秒超时明确异常定义业务异常区分不同错误场景空值检查确保返回数据不为null4. 多线程环境下的异常捕获多线程编程中异常处理容易被忽视。看一个典型问题public void ProcessDataInParallel(ListDataItem items) { Parallel.ForEach(items, item { try { ProcessItem(item); } catch (Exception ex) { Console.WriteLine($处理{item.Id}出错: {ex.Message}); } }); }这段代码的问题异常丢失Parallel.ForEach会聚合异常但这里被吞没了缺乏上下文控制台输出不足以记录完整的异常信息没有重试失败的任务直接丢弃没有恢复机制资源竞争没有考虑共享资源的线程安全问题改进后的并行处理public async Task ProcessDataSafelyAsync(ListDataItem items) { var options new ParallelOptions { MaxDegreeOfParallelism Environment.ProcessorCount, CancellationToken _cancellationTokenSource.Token }; var exceptions new ConcurrentQueueException(); var retryItems new ConcurrentBagDataItem(); await Task.Run(() { Parallel.ForEach(items, options, item { options.CancellationToken.ThrowIfCancellationRequested(); try { ProcessItem(item); } catch (OperationCanceledException) { throw; } catch (Exception ex) when (IsTransientError(ex)) { _logger.LogWarning(ex, 临时错误将重试: {ItemId}, item.Id); retryItems.Add(item); } catch (Exception ex) { _logger.LogError(ex, 处理失败: {ItemId}, item.Id); exceptions.Enqueue(ex); } }); }); if (!retryItems.IsEmpty) { _logger.LogInformation(重试{Count}个失败项, retryItems.Count); await ProcessDataSafelyAsync(retryItems.ToList()); } if (!exceptions.IsEmpty) { throw new AggregateException(部分处理失败, exceptions); } } private bool IsTransientError(Exception ex) { return ex is TimeoutException or HttpRequestException or { Message: Resource temporarily unavailable }; }优化要点并行度控制根据CPU核心数设置合理的并行度取消支持响应取消请求优雅终止异常聚合收集所有异常最后统一抛出临时错误重试识别可重试错误自动重试线程安全集合使用ConcurrentQueue和ConcurrentBag避免竞争5. 第三方库集成中的异常处理集成第三方库时异常处理需要考虑更多边界情况。看一个不安全的实现public Result ProcessWithThirdParty(InputData input) { try { var lib new ThirdPartyLibrary(); return lib.Process(input); } catch (Exception ex) { _logger.LogError(ex, 处理失败); return Result.Failure; } }问题分析资源泄漏ThirdPartyLibrary可能实现了IDisposable但未释放异常泛化捕获所有Exception难以区分业务错误和系统错误缺乏超时库方法可能无限阻塞没有初始化检查未验证库的可用性安全的第三方库集成public async TaskResult ProcessWithThirdPartySafelyAsync(InputData input) { // 验证输入 if (input null) throw new ArgumentNullException(nameof(input)); if (!input.IsValid()) throw new InvalidOperationException(无效输入); await using var lib new ThirdPartyLibrary(); try { // 初始化检查 if (!await lib.CheckHealthAsync()) throw new ThirdPartyServiceException(服务不可用); // 执行带超时的操作 using var cts new CancellationTokenSource(TimeSpan.FromSeconds(30)); return await lib.ProcessAsync(input, cts.Token); } catch (ThirdPartyTimeoutException ex) { _logger.LogWarning(ex, 第三方服务超时); throw new ThirdPartyServiceException(请求超时, ex); } catch (ThirdPartyBusinessException ex) { _logger.LogInformation(ex, 业务规则拒绝: {Code}, ex.ErrorCode); return Result.FromErrorCode(ex.ErrorCode); } catch (ThirdPartyAuthException ex) { _logger.LogError(ex, 认证失败); throw new ThirdPartyServiceException(认证失败, ex); } catch (Exception ex) when (IsRecoverable(ex)) { _logger.LogWarning(ex, 可恢复错误将重试); return await ProcessWithThirdPartySafelyAsync(input); } catch (Exception ex) { _logger.LogError(ex, 第三方服务错误); throw new ThirdPartyServiceException(处理失败, ex); } } private bool IsRecoverable(Exception ex) { return ex is ThirdPartyRateLimitException or ThirdPartyTemporaryException or { Message: Try again later }; }最佳实践输入验证前置检查尽早失败资源释放确保第三方资源正确释放健康检查预先验证服务可用性超时控制防止无限等待异常分类区分业务异常和系统异常可恢复错误识别并自动重试临时错误异常处理的高级模式除了上述场景特定的解决方案还有一些通用的高级异常处理模式值得掌握异常过滤器C# 6.0引入的异常过滤器可以更精确地控制catch块的执行try { // 可能抛出多种异常的代码 } catch (HttpRequestException ex) when (ex.StatusCode HttpStatusCode.NotFound) { // 只处理404错误 } catch (HttpRequestException ex) when (ex.StatusCode HttpStatusCode.Unauthorized) { // 只处理401错误 }全局异常处理对于未捕获的异常应实现全局处理机制// ASP.NET Core中的全局过滤器 public class GlobalExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { var ex context.Exception; var problemDetails new ProblemDetails { Title 内部服务器错误, Status (int)HttpStatusCode.InternalServerError, Detail ex.Message, Instance context.HttpContext.Request.Path }; if (ex is BusinessException businessEx) { problemDetails.Title 业务错误; problemDetails.Status (int)HttpStatusCode.BadRequest; problemDetails.Extensions[errorCode] businessEx.ErrorCode; } context.Result new ObjectResult(problemDetails) { StatusCode problemDetails.Status }; context.ExceptionHandled true; } }防御性编程技巧空对象模式避免null引用异常public interface ILogger { void Log(string message); } public class NullLogger : ILogger { public void Log(string message) { /* 什么都不做 */ } }参数验证使用Guard Clausepublic class Validator { public static void NotNullT(T value, string paramName) where T : class { if (value null) throw new ArgumentNullException(paramName); } public static void InRange(int value, int min, int max, string paramName) { if (value min || value max) throw new ArgumentOutOfRangeException(paramName, $应在{min}-{max}之间); } } // 使用 public void Process(Order order) { Validator.NotNull(order, nameof(order)); Validator.InRange(order.Quantity, 1, 100, nameof(order.Quantity)); // 业务逻辑 }结果对象模式替代异常public class ResultT { public bool IsSuccess { get; } public T Value { get; } public string Error { get; } private Result(T value, bool isSuccess, string error) { Value value; IsSuccess isSuccess; Error error; } public static ResultT Success(T value) new(value, true, null); public static ResultT Failure(string error) new(default, false, error); } public ResultCustomer GetCustomer(int id) { try { var customer _repository.GetById(id); return customer ! null ? ResultCustomer.Success(customer) : ResultCustomer.Failure(客户不存在); } catch (Exception ex) { return ResultCustomer.Failure(ex.Message); } }异常处理性能考量不当的异常处理会对性能产生显著影响。以下是一些性能优化建议避免在正常流程中使用异常// 不好 - 使用异常控制流程 try { var value dict[key]; } catch (KeyNotFoundException) { // 处理键不存在 } // 更好 - 使用TryGetValue if (!dict.TryGetValue(key, out var value)) { // 处理键不存在 }预检查条件// 不好 try { File.Delete(path); } catch (IOException) { // 处理失败 } // 更好 if (File.Exists(path)) { try { File.Delete(path); } catch (IOException) { // 处理失败 } }使用ExceptionDispatchInfo保留堆栈Exception? exception null; try { // 可能抛出异常的代码 } catch (Exception ex) { exception ex; } if (exception ! null) { ExceptionDispatchInfo.Capture(exception).Throw(); }避免深层异常嵌套// 不好 - 深层嵌套 try { try { try { // 代码 } catch (SpecificException ex) { // 处理 } } catch (AnotherException ex) { // 处理 } } catch (Exception ex) { // 处理 } // 更好 - 扁平化 try { // 代码 } catch (SpecificException ex) { // 处理 } catch (AnotherException ex) { // 处理 } catch (Exception ex) { // 处理 }异常日志记录最佳实践有效的日志记录对问题诊断至关重要结构化日志// 不好 - 非结构化 _logger.LogError($错误发生在{methodName}: {ex.Message}); // 更好 - 结构化 _logger.LogError(ex, 错误发生在{Method}, methodName);敏感信息过滤try { // 处理包含敏感信息的操作 } catch (Exception ex) { var sanitizedEx new Exception(ex.Message); // 剥离敏感数据 _logger.LogError(sanitizedEx, 处理失败); throw; }上下文丰富public class ContextualLogger { private readonly ILogger _logger; private readonly Dictionarystring, object _context; public ContextualLogger(ILogger logger) { _logger logger; _context new Dictionarystring, object(); } public void AddContext(string key, object value) _context[key] value; public void LogError(Exception ex, string message) { using (_logger.BeginScope(_context)) { _logger.LogError(ex, message); } } }日志级别选择Trace最详细的调试信息Debug开发环境有用的调试信息Information应用程序流程跟踪Warning异常或意外事件但应用程序继续运行Error需要立即调查的错误Critical导致应用程序崩溃的灾难性故障测试异常处理代码确保异常处理逻辑正确同样需要测试单元测试异常[Test] public void Transfer_ShouldThrow_WhenInsufficientFunds() { var account new Account(100); Assert.ThrowsInsufficientFundsException(() account.Transfer(200, recipient)); }模拟异常[Test] public async Task GetWeather_ShouldRetry_OnTimeout() { var mockHttp new MockHttpMessageHandler(); mockHttp.When(*).Respond(async () { await Task.Delay(1000); throw new TaskCanceledException(); }); var client new HttpClient(mockHttp) { Timeout TimeSpan.FromMilliseconds(500) }; var service new WeatherService(client); await Assert.ThrowsAsyncWeatherServiceException(() service.GetWeatherAsync(London)); // 验证重试次数 mockHttp.VerifyNoOutstandingExpectation(); }集成测试资源泄漏[Test] public void ProcessFile_ShouldNotLeakHandles() { var handleCountBefore GetFileHandleCount(); try { FileProcessor.ProcessFile(test.txt); } catch { // 即使失败也不应泄漏 } var handleCountAfter GetFileHandleCount(); Assert.AreEqual(handleCountBefore, handleCountAfter); }压力测试异常路径[Test] public void ConcurrentErrors_ShouldNotDeadlock() { Parallel.For(0, 100, i { try { FaultyService.DoSomethingThatFailsOften(); } catch { // 预期中的错误 } }); // 如果没有死锁测试通过 Assert.Pass(); }异常处理的文化与规范除了技术实现团队还应该建立一致的异常处理文化团队规范定义哪些异常应该捕获哪些应该传播规定日志记录的标准格式确定自定义异常的类型体系制定异常处理代码的审查标准文档实践/// summary /// 更新用户信息 /// /summary /// exception crefArgumentNullException当user为null时抛出/exception /// exception crefDataAccessException当数据库操作失败时抛出/exception /// exception crefConcurrencyException当并发冲突时抛出/exception public void UpdateUser(User user) { // 实现 }代码审查要点检查是否捕获了过于宽泛的Exception验证资源是否在finally块或using语句中释放确认敏感信息没有记录到日志中确保异常提供了足够的诊断信息监控与告警配置异常监控仪表盘设置关键异常的实时告警定期分析异常趋势建立异常分类和优先级体系总结与持续改进异常处理不是一次性的任务而需要持续改进事后分析对生产环境中的未处理异常进行根本原因分析记录异常处理决策和后续行动将经验教训转化为团队知识指标监控跟踪异常发生率监控异常处理性能开销测量异常解决时间模式演进定期评审异常处理策略随着系统演进调整异常类型体系根据新的技术能力优化实现工具建设开发自定义异常分析工具构建异常重放测试环境创建异常处理代码生成器通过将这些实践应用到你的C#项目中可以构建出更加健壮、可维护的异常处理体系显著提升应用程序的可靠性和可观测性。记住好的异常处理不是关于防止所有错误而是关于优雅地处理不可避免的错误并提供足够的上下文来快速诊断和解决问题。