多个catch块必须按“从具体到通用”顺序排列,否则编译失败;通用异常(如Exception)若排在前面,将导致后续具体异常无法被捕获。

多个catch块的顺序为什么不能乱
捕获多种异常时,catch块的排列顺序直接决定哪些异常能被真正处理。如果把通用类型(如Exception)写在前面,后面所有更具体的异常(比如NullReferenceException、IndexOutOfRangeException)就永远进不去——它们全被父类“吃掉”了。
常见错误现象:catch(Exception ex) 放在第一个,结果日志里只看到“未处理的 DivideByZeroException”,实际它根本没触发任何 catch 块。
- 必须按继承链从具体到宽泛排列:先
catch(DivideByZeroException),再catch(ArgumentException),最后才是catch(Exception) - 编译器不会报错,但运行时逻辑完全失效
- 调试时注意堆栈中实际抛出的异常类型,别只看
ex.GetType().Name输出,要确认是不是被上层catch提前截断了
用 when 子句做条件过滤比多写 catch 更轻量
when 是 C# 6.0 引入的语法糖,它允许你在同一个 catch 块里加判断条件,避免为相似场景重复写一堆几乎一样的 catch。比如你只想拦截含特定关键词的 IOException,而不是所有 IO 错误。
典型使用场景:网络重试逻辑中区分“连接拒绝”和“超时”,或日志归类时按错误消息内容分流。
- 语法是
catch(IOException ex) when (ex.Message.Contains("timeout")) - 条件表达式必须是纯计算,不能有副作用(比如不能在
when里调用Log.Error()) - 多个
when同时匹配时,只执行第一个满足条件的catch块 - 性能上比嵌套
if稍好,因为条件在异常分发阶段就求值,不进块就不执行后续逻辑
GetBaseException() 在多层包装异常时怎么用
第三方库或框架(比如 Entity Framework、HttpClient)常把原始异常包一层再抛出,形成 AggregateException 或带 InnerException 的链式结构。此时直接看 ex.Message 往往只能看到外层提示,真实根因藏在深处。
这时候不能只依赖 catch 类型,得主动挖。
- 用
ex.GetBaseException()一步拿到最内层那个原始异常实例 - 配合
is检查类型更可靠:if (ex.GetBaseException() is SqlException sqlEx) - 不要用
ex.InnerException?.InnerException手动递归,容易空引用或漏掉AggregateException.InnerExceptions - 日志记录时建议同时输出
ex.ToString(),它自动展开整个异常链
异步方法里多个异常会打包成 AggregateException
在 async/await 中,如果 Task.WhenAll 或并行操作里多个子任务失败,最终抛出的不是单个异常,而是 AggregateException,里面装着所有失败原因。直接 catch(Exception) 会错过细节。
容易踩的坑:以为 try-catch 能拿到每个子异常,结果只捕获到一个“汇总异常”,且默认 .Message 只显示数量,不列具体内容。
- 必须显式解包:
catch(AggregateException ae) { foreach (var e in ae.InnerExceptions) { /* 处理每个e */ } } - 也可以用
ae.Flatten()把嵌套的AggregateException展平成单层列表 - 若只关心第一个错误,可用
ae.InnerException,但不推荐——可能掩盖并发问题的全貌 - 在
await单个Task时不会出现这个情况,只有组合多个Task时才触发
多异常处理真正的复杂点不在语法,而在异常来源的不可控性:框架封装、异步聚合、跨线程传播、自定义异常继承深度。别迷信 catch 列表能覆盖一切,关键是要有主动拆解和追溯的意识。


















