catch不写throw等于吞异常,必须重抛或转为业务异常;资源释放优先using而非finally;自定义异常需三个标准构造函数;高频路径避免滥用try/catch。

catch 里不写 throw 就等于吞掉异常
很多人以为 catch 块里加个日志就叫“处理了异常”,其实只要没重新抛出,上层完全感知不到出过事。尤其在库函数或异步方法里,静默吃掉 NullReferenceException 或 ArgumentException,调用方只会看到返回值不对或直接卡死。
- 真正要“处理”:要么修复状态(比如重试、降级)、要么明确转成业务可理解的错误(如把
SqlException转成OrderPersistenceFailedException) - 想记录后继续传播:必须写
throw(不是throw ex),否则堆栈从这里截断 - 异步方法中
await后的catch捕获的是AggregateException内部的原始异常,直接catch (Exception ex)是对的,但别误以为它包了一层就该解包
finally 不是万能收尾,资源释放优先用 using
finally 确实保证执行,但它不解决资源泄漏——比如忘了在 finally 里调 Dispose(),或者 Dispose() 自己又抛异常,反而掩盖主异常。
- 所有实现
IDisposable的对象(FileStream、SqlConnection、HttpClient实例等),优先写using语句,编译器自动展开成try/finally+Dispose() -
finally真正适合的场景:清理非托管资源(如手动Marshal.AllocHGlobal)、重置全局标志位、记录执行耗时(即使出错了也要记) - 注意:如果
try中发生异常,且finally也抛异常,后者会覆盖前者,导致原始错误丢失
自定义异常类必须继承 Exception,且带三个标准构造函数
只加个 public class MyBusinessException : Exception 是不够的。.NET 序列化、调试器、日志框架都依赖那三个构造函数:空参、string message、string message, Exception inner。缺一个,跨 AppDomain 或序列化时就会报 SerializationException。
- 不要只重写
ToString()或加新属性就完事;新属性得标记[Serializable],且在三个构造函数里正确传递/反序列化 - 业务异常建议命名以
Exception结尾(如InsufficientBalanceException),避免和领域对象混淆 - 不要为每个错误码建一个异常类——用枚举字段 + 消息模板更可控;异常类型应反映错误本质(是验证失败?还是外部服务不可用?)
try/catch 不是日志开关,高频路径上别滥用
在循环体、属性 getter、数学计算等本不该出错的地方硬套 try/catch,性能损耗明显。JIT 对异常处理有专门优化,但前提是“异常确实罕见”。一旦变成常规控制流,CPU 分支预测失败、堆栈展开开销都会拖慢 10 倍以上。
- 替代方案:用
int.TryParse()代替int.Parse()+catch;用Dictionary.TryGetValue()代替dict[key]+catch KeyNotFoundException - ASP.NET Core 中,全局异常中间件(
UseExceptionHandler)比在每个 Controller 里写try/catch更干净,也避免重复日志 - 单元测试里故意触发异常没问题,但生产代码中,
catch (Exception)必须有明确兜底逻辑,不能只写// TODO: handle
最常被忽略的其实是异常的语义粒度——同一个 catch 块里混着处理网络超时、JSON 解析失败、权限不足,后续定位时根本分不清是哪一层崩了。按异常类型分层捕获,比堆一堆 if (ex.Message.Contains("timeout")) 可靠得多。


















