一、核心规则
- 异常仅用于非预期错误,禁止用来做业务逻辑判断(替代
if/TryXXX)。 - 精准捕获:抓具体异常,禁止无脑捕获
Exception。 - 禁止空捕获
catch{}、吞异常、隐藏故障。 - 重抛异常只用裸
throw;,禁用throw ex;(丢失堆栈)。 - 资源释放优先
using,少手写finally。 - 优先使用
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 项目实践
- 业务层 / 仓储层:只捕获可恢复、可预知的特定异常 +
when过滤。 - Controller / 接口层:不手写大量
try-catch。 - 全局统一异常中间件 / 过滤器兜底:
- 记录完整异常 + 堆栈日志;
- 屏蔽原始错误堆栈返回前端;
- 输出标准化错误响应。
六、总结
- 抓具体,不抓
Exception; - 能用
when过滤,不写 Catch 内部 If; - 重抛用
throw,拒绝throw ex; - 异常不吞、必留日志;
- 正常判断用 TryParse/If,不走异常;
- 资源一律 using,干净安全。
