using语句本质是编译器生成try-finally的语法糖,确保Dispose()在finally中执行;C#8+支持声明式using和await using,后者专用于IAsyncDisposable。

using 语句本质是 try-finally 的语法糖
它不是“自动释放资源”的魔法,而是编译器帮你把 Dispose() 调用塞进 finally 块。哪怕代码里抛出异常、提前 return 或 goto,Dispose() 仍会被执行。
反例:直接调用 obj.Dispose() 而不包在 try-finally 中,一旦构造后、Dispose前发生异常,资源就泄漏了。
-
using只对实现了IDisposable的类型有效;没实现该接口的对象用using会编译失败 - 编译后 IL 中看不到
using,只有try/finally和显式的Dispose()调用 - 不要在
using块内把变量赋为null——这不影响finally中的Dispose()调用,但可能掩盖空引用判断逻辑
嵌套 using 和 using 声明(C# 8+)的区别
老写法 using (var fs = new FileStream(...)) { ... } 是语句块,作用域限于大括号内;新写法 using var fs = new FileStream(...); 是“声明式 using”,变量作用域延续到当前代码块末尾(比如方法结尾),且支持多个连续声明。
关键差异在于生命周期控制点:声明式 using 的 Dispose() 在作用域结束时触发,不是大括号结束时——这对异步方法或早期 return 场景影响明显。
- 多个声明式
using按声明顺序创建,按**逆序**调用Dispose()(和嵌套using块一致) - 声明式
using不能用于catch或finally块内部(编译错误:CS8624) - 若变量在声明后被重新赋值(如
fs = anotherStream),原对象会在作用域结束时被 Dispose,新对象不会——这是常见误用点
using 与 async/await 组合时的陷阱
using 本身不支持异步释放,即它调用的是同步的 IDisposable.Dispose(),而非 IAsyncDisposable.DisposeAsync()。混用会导致异步资源(如 HttpClient 实例背后的连接池、SqlDataReader)未真正清理干净。
正确做法是用 await using(C# 8+),它专为 IAsyncDisposable 设计。
-
await using var client = new HttpClient();编译后调用的是DisposeAsync().AsTask(),而非Dispose() -
await using不支持表达式形式(如await using (var x = Exp())),只能用声明式写法 - 混合使用
using和await using在同一作用域时,注意释放顺序:同步Dispose()总在DisposeAsync()完成后才开始(因为await using的finally等待了异步操作)
IDisposable 实现不到位导致 using 失效
很多自定义类声称“支持 using”,但 Dispose() 方法体为空、只释放部分资源、或未遵循 Dispose 模式(比如没处理重复调用、没抑制终结器),都会让 using 形同虚设。
典型症状:程序运行久了内存缓慢上涨、文件句柄耗尽、数据库连接数打满——而日志里没有异常。
-
Dispose()必须可被多次安全调用;建议开头加if (_disposed) return;+_disposed = true; - 托管资源(如其他
IDisposable对象)应在Dispose()中显式调用其Dispose();非托管资源(如IntPtr)需用Marshal.FreeHGlobal等清理 - 若类有终结器(
~MyClass()),必须在Dispose()中调用GC.SuppressFinalize(this),否则终结器线程可能在Dispose()后再次释放已释放的资源
实际项目里,最常被忽略的是 await using 和 IAsyncDisposable 的配合,以及 Dispose() 中对重复调用的防护——这两处出问题,往往要等压测或上线几天后才暴露。


















