c#中try-catch仅用于已知可恢复的异常场景,需能做出有意义响应;滥用catch(exception)或忽略异常会导致定位困难、资源泄漏和逻辑缺陷掩盖。

直接说结论:C# 中 try-catch 不是用来“兜底所有错误”的,而是为**已知可恢复的异常场景**设计的;滥用 catch (Exception) 或忽略 ex 会导致问题更难定位、资源泄漏、甚至掩盖逻辑缺陷。
什么时候该用 try-catch,什么时候不该用
核心判断标准是:你是否能对这个异常做**有意义的响应**。比如用户选了一个不存在的文件,你可以弹窗提示重选;但如果是 NullReferenceException,说明代码里漏了空值检查,该修的是逻辑,不是加 catch。
- 该捕获:明确知道异常类型且有对应策略,如
FileNotFoundException(提示用户检查路径)、SqlException(重试或降级)、HttpRequestException(显示网络错误) - 不该捕获:泛型
catch (Exception)且只写个Console.WriteLine("出错了");或者捕获后吞掉异常、不记录、不 rethrow - 特别注意:
StackOverflowException和OutOfMemoryException通常无法安全捕获,CLR 可能不允许进入catch块
catch 块顺序和类型筛选必须严格按继承层级写
多个 catch 块的执行顺序是从上到下匹配第一个能处理该异常的类型。如果把基类放前面,派生类就永远进不去。
错误写法:
try { /* ... */ }
catch (Exception ex) { /* 这里会吃掉所有异常,下面的永远不会执行 */ }
catch (FileNotFoundException ex) { /* 永远不会走到这里 */ }
正确写法:
try { /* ... */ }
catch (FileNotFoundException ex) { /* 先处理最具体的 */ }
catch (IOException ex) { /* 再处理它的父类 */ }
catch (Exception ex) { /* 最后兜底,但必须包含 log 或 rethrow */ }
- 所有自定义异常也必须继承
Exception,否则无法被标准catch捕获 -
catch后不带类型(即裸catch { })在 .NET 6+ 已被标记为过时,编译器会警告
finally 是释放资源的唯一可靠位置,别信 catch 里的清理
哪怕 try 里发生未捕获异常、线程被中止、或 catch 块自己抛出新异常,finally 里的代码仍会执行(极少数情况如进程强制退出除外)。
- 数据库连接、文件流、HttpClient 实例等,只要调用了
Dispose(),就该放进finally或用using语句(后者本质是编译器生成try-finally) - 避免在
finally里 throw 新异常——它会覆盖原始异常,导致根因丢失 - 不要在
finally里写可能失败的操作(如再次写日志),除非你确定它不会抛异常
throw 和 throw ex 的区别决定调试效率
在 catch 块里想“继续向上抛”,必须用 throw(无参数),而不是 throw ex。后者会清空原始堆栈跟踪,让错误看起来像是从 catch 块里新抛出来的。
try { DoSomething(); }
catch (ArgumentException ex)
{
LogError(ex);
throw; // ✅ 保留原始堆栈
// throw ex; // ❌ 堆栈从这行开始,丢失原始位置
}
- 如果要包装异常(比如加业务上下文),用
throw new InvalidOperationException("业务操作失败", ex),第二个参数传原异常,堆栈会完整保留 -
throw后面不能跟表达式,只能是throw;或throw new XxxException(...)
最容易被忽略的一点:try 块本身不产生开销,但一旦触发异常,性能代价极高——CLR 要展开整个调用栈找 catch。所以别用异常来控制正常流程,比如用 int.Parse() 配 catch (FormatException) 做输入校验,该换 int.TryParse()。











