何时抛出ObjectDisposedException?探讨CLR via C#相关建议的落地问题
关于ObjectDisposedException的两个问题解答
1. 何时应该抛出ObjectDisposedException?
简单来说,当一个已经被释放(调用过Dispose()或被垃圾回收器终结)的对象,收到了需要依赖其未释放资源的请求时,就应该抛出这个异常。举几个典型场景:
- 比如一个
FileStream对象被Dispose()后,调用它的Read()、Write()方法——这些操作需要访问底层的文件句柄,而这个资源已经被释放,此时必须抛出ObjectDisposedException。 - 再比如一个自定义的数据库连接类,Dispose后调用
ExecuteQuery()方法,同样因为数据库连接资源已释放,应该抛出异常。
反之,如果某个方法/属性只依赖对象在内存中缓存的数据,不涉及已释放的非托管资源,那完全不需要抛出这个异常。比如一个已Disposed的HttpClient对象,访问它的BaseAddress属性(这个值存在内存里),就没必要抛异常。
2. 为什么实际开发中很少遵循Richter的建议?
我在日常开发和研究.NET框架源码时也发现这个反差,原因其实挺务实的,不是开发者不知情,而是这个理论建议在落地时要考虑很多现实因素:
- 开发成本过高:给一个类的所有方法、属性都加上
IsDisposed检查,尤其是大型类(比如WinForms的控件、复杂的业务对象),工作量会陡增。而且很多方法在对象Disposed后根本不会被调用,这种投入的产出比极低。 - 实际场景中很少触发:多数情况下,我们会用
using块来管理实现IDisposable的对象,块结束后对象就会超出作用域,后续几乎不会再被引用。Disposed后还调用对象成员的情况,大多是代码bug,而这类bug在测试阶段很容易被发现,不需要靠全量检查来兜底。 - 框架本身也没严格执行:.NET官方框架里很多实现
IDisposable的类,也没有给所有成员加异常抛出。比如Form控件Disposed后,访问它的Tag属性(存储内存数据)不会抛异常;MemoryStreamDisposed后,访问Length属性也不会抛——因为这些操作不依赖已释放的资源。 - 调试体验问题:如果每个成员都抛异常,在一些复杂的异步或多线程场景中,可能会出现很多“误报”的异常。比如对象被意外Disposed但没有影响核心逻辑,此时抛出异常反而会干扰调试流程。
- 语义合理性:有些成员本身就是用来判断对象状态的,比如
IsDisposed属性——如果访问它还要抛ObjectDisposedException,那完全是自相矛盾,这类成员肯定不能加检查。
内容的提问来源于stack exchange,提问作者BanksySan




