Versions:2016-10-17 2016-11-01 2017-05-10 2017-11-01 2017-12-02 2018-03-05 2019-11-18 2020-02-17 2024-04-16

可恶的 C# IDisposable 接口

我们 team 快被 C# 里面的各种 IDisposable 对象给折腾疯了…… 故事比较长,先来科普一下。如果你没有用过 C#,IDisposable 是 C# 针对“资源管理”设计的一个接口,它类似于 Java 的 Closeable 接口。这类接口一般提供一个“方法”(比如叫 Dispose 或者 Close),你的资源(比如文件流)实现这个接口。使用资源的人先“打开资源”,用完之后调用这个方法,表示“关闭资源”。比如,文件打开,读写完了之后,调用 close 关掉,就是这个原理。

相比于 Java,C# 大部分时候是更好的语言,然而它并没有全面超越 Java。一个显著的不足之处就是 C# 的 IDisposable 接口引起的头痛,要比 Java 的 Closeable 大很多。经过我分析,这一方面是因为 .NET 库代码里面实现了很多没必要的 IDisposable,以至于你经常需要思考如何处理它们。另一方面是由于微软的编码规范和 Roslyn 静态分析引起的误导,使得用户对于 IDisposable 接口的“正确使用”过度在乎,导致代码无端变得复杂,导致 IDisposable 在用户代码里面传染。

回来说说我们的代码,本来没那么多问题的,结果把 Roslyn 静态分析一打开,立马给出几百个警告,说“你应该调用 Disposable 成员的 Dispose 方法”(CA2213),或者说“类型含有 disposable 成员,却没有实现 IDisposable 接口”(CA1001)。奇葩的是,C# 里面有些很小却很常用的对象,包括 ManualResetEvent, Semaphore, ReaderWriterLockSlim 都实现了 IDisposable 接口,所以经常搞得你不知所措。按官方的“规矩”,你得显式的调用所有这些对象的 Dispose 方法进行“释放”,而不能依赖 GC 进行回收。所以你的代码经常看起来就像这个样子:

void foo()
{
  var event = new ManualResetEvent(false);
  // 使用 _event ...
  event.Dispose();
}