C#异常处理必须按“从具体到通用”排列catch块,否则编译失败;禁用空catch和throw ex;优先用using替代finally;善用when筛选异常;始终用throw;保留原始堆栈。

catch 顺序写反了会直接编译失败
你写了多个 catch 块,但把通用异常放前面、具体异常放后面,C# 编译器会立刻报错——不是运行时警告,是根本通不过编译。Exception 派生自所有异常类型,如果它排在 ArgumentNullException 前面,后者永远没机会执行。
- 必须按“从具体到通用”排列:先
catch (ArgumentNullException ex),再catch (IOException ex),最后才考虑catch (Exception ex) - 空
catch块(catch { })虽然语法合法,但等于掩埋错误,调试时完全找不到线索,2025 年起已被主流代码扫描工具标为高危问题 - 别用
catch (Exception e) { throw e; }——这会重置堆栈跟踪,让错误源头消失;要用throw;(不带参数)才能保留原始调用链
finally 不是万能收尾,资源释放得看类型
finally 确实保证执行,但它不解决“何时释放”和“是否安全”的问题。比如数据库连接或文件流,如果在 try 中根本没成功创建,finally 里直接 .Close() 就可能抛出 NullReferenceException。
- 优先用
using语句替代手动finally:它自动调用IDisposable.Dispose(),且在对象为null时不会出错 - 如果必须手写
finally,记得判空:if (fileStream != null) fileStream.Close(); -
finally里抛异常会覆盖原异常——除非你真想吞掉前面的错误,否则别在里面写可能失败的操作
用 when 筛选异常比 catch 后 if 判断更干净
遇到类似“只处理磁盘满导致的 IOException,其他 IO 错误走默认逻辑”这种需求,老办法是在 catch (IOException ex) 里加 if (ex.Message.Contains("disk full")),但这样会让错误处理逻辑分散、难以测试。
- C# 6.0+ 支持
when子句:catch (IOException ex) when (ex.Message.Contains("disk full")) -
when表达式在异常匹配时求值,不满足条件就跳过该catch,继续找下一个——行为更明确,也避免了无谓的变量捕获 - 注意:
when里不要写耗时操作(比如网络请求),它在异常传播路径上执行,会影响性能
throw 和 throw ex 的堆栈差异肉眼可见
你在日志里看到的 StackTrace 是排查问题的关键线索。throw ex; 会让堆栈从当前行重新开始,原始出错位置彻底丢失;而 throw;(分号前无表达式)会原样保留原始堆栈,连第几行、哪个方法调用的都清清楚楚。
- 只要不是做全局错误包装(比如统一加 TraceId),一律用
throw; - 需要补充上下文又不想丢堆栈?用
ExceptionDispatchInfo.Capture(ex).Throw();,但这是高级用法,普通业务逻辑没必要 - 自定义异常类时,务必调用基类构造函数传递
innerException,否则嵌套异常的原始信息也会断掉
真正难的从来不是写 try-catch,而是判断哪一行值得包、哪一层该拦截、哪条消息必须透传——这些没法靠语法记住,得靠每次线上查问题时多看两眼堆栈。


















