.NET 规范异常捕获 & 处理

一、核心规则

 

  1. 异常仅用于非预期错误,禁止用来做业务逻辑判断(替代 if/TryXXX)。
  2. 精准捕获:抓具体异常,禁止无脑捕获 Exception
  3. 禁止空捕获 catch{}、吞异常、隐藏故障。
  4. 重抛异常只用裸 throw;,禁用 throw ex;(丢失堆栈)。
  5. 资源释放优先 using,少手写 finally
  6. 优先使用 when 异常过滤器,缩小捕获范围,代码更干净。

 


 

二、高频错误写法

 

1. 全量捕获

 

// 错误:所有错误全吃掉,线上Bug无法排查
try
{
    
}
catch (Exception ex)
{
    Log(ex);
}

 

2. 空捕获 / 静默失败

 

// 极端错误:完全吞掉异常,无日志无提示
try { }
catch
{

}

 

3. 截断异常堆栈

 

catch (SqlException ex)
{
    throw ex; // 错误:重置调用堆栈,排查直接报废
}

 

4. 异常替代常规判断

 

// 错误:用异常控制正常业务
try
{
    int id = int.Parse(input);
}
catch
{
    id = 0;
}

// 正确:预判优先
int.TryParse(input, out int id);

 


 

三、标准正确写法

 

1. 基础:精准捕获指定异常

 

try
{
    string text = File.ReadAllText("data.txt");
}
catch (FileNotFoundException ex)
{
    _logger.LogWarning(ex, "目标文件不存在");
    // 友好业务提示 / 降级处理
}
catch (IOException ex)
{
    _logger.LogError(ex, "文件读写失败");
}

 

2. 进阶:when 过滤器

  在捕获前做条件过滤,不进入 Catch 代码块、不破坏堆栈,替代 Catch 内部
if。  

try
{
    await _dbContext.SaveChangesAsync();
}
// 仅捕获 SQL 唯一键冲突
catch (SqlException ex) when (ex.Number is 2627)
{
    throw new BusinessException("数据重复,请勿重复提交");
}
// 仅捕获外键约束错误
catch (SqlException ex) when (ex.Number is 547)
{
    throw new BusinessException("关联数据不存在,操作失败");
}
// 仅捕获数据库死锁
catch (SqlException ex) when (ex.Number is 1205)
{
    throw new BusinessException("系统繁忙,请稍后重试");
}

 

3. 保留原始堆栈重抛

 

catch (TimeoutException ex)
{
    _logger.LogError(ex, "服务调用超时");
    throw; // 正确:保留完整堆栈、调用链
}

 

4. 包装自定义业务异常(内层保留原异常)

 

catch (ArgumentNullException ex)
{
    // 第二个参数传入原异常,完整保留错误链路
    throw new BusinessException("必填参数不能为空", ex);
}

 

5. finally 资源兜底

 

FileStream? stream = null;
try
{
    stream = new FileStream(path, FileMode.Open);
}
catch (IOException ex)
{
    _logger.LogError(ex, "文件操作异常");
}
finally
{
    // 无论成败,必然释放
    stream?.Dispose();
}

 

优先替代方案:
using var stream = new FileStream(path, FileMode.Open);  


 

四、多层捕获 + 过滤

 

try
{
    // 数据库业务操作
}
catch (SqlException ex) when (ex.Number is 2627 or 547)
{
    // 针对性约束错误处理
}
catch (SqlException ex)
{
    // 其他数据库异常统一日志
    _logger.LogError(ex, "数据库执行异常");
    throw;
}
catch (OperationCanceledException)
{
    // 单独处理取消请求
}

 


 

五、ASP.NET Core 项目实践

 

  1. 业务层 / 仓储层:只捕获可恢复、可预知的特定异常 + when 过滤。
  2. Controller / 接口层:不手写大量 try-catch
  3. 全局统一异常中间件 / 过滤器兜底:
    • 记录完整异常 + 堆栈日志;
    • 屏蔽原始错误堆栈返回前端;
    • 输出标准化错误响应。

     

 


 

六、总结

 

  1. 抓具体,不抓 Exception
  2. 能用 when 过滤,不写 Catch 内部 If;
  3. 重抛用 throw,拒绝 throw ex
  4. 异常不吞、必留日志;
  5. 正常判断用 TryParse/If,不走异常;
  6. 资源一律 using,干净安全。

文章摘自:https://www.cnblogs.com/chuansheng/p/19935530