码农深耕 - 说说IDisposable
概要
C#提供了方便的垃圾回收机制,使我们几乎不再需要为资源管理费心。可事实上,能被垃圾回收释放掉的只是托管资源,非托管资源还是需要我们手动释放。而为了实现这一目的,C#提供了 IDisposable 接口,这篇文章就谈一谈 IDisposable 接口在使用中需要注意的地方。
实现
首先,IDisposable 接口非常简单,只包含一个方法 Dispose。在 IDisposable 接口的定义中可以看到明确的描述,它是用于释放非托管资源的 [1]。但是,想写出一个健壮的 IDisposable 实现却不是那么容易,好在微软为我们提供了一份详细的指南 [2][3],参照这份指南中的示例代码,我们就可以轻而易举的写出一份优秀的 IDisposable 接口实现代码了,这里不再进行详细说明。
那么那些资源是常见的非托管资源呢?根据我的经验,列举如下:
- 文件流
- 窗体句柄
- 图片
- 网络连接
- 数据库连接
在使用到上述资源时,不要忘记务必在使用之后调用它们的 Dispose 方法。为了保证资源释放,一般我们会利用 try / finally 块,在 finally 块中调用 Dispose 方法。针对这种需求,C# 为我们提供了 using 语法糖 [4],对于实现了 IDisposable 接口的对象,利用 using 语句,可以简单的完成资源释放。
需要注意的细节
注意事件退订
当我们调用了一个对象的 Dispose 方法之后,它的非托管资源就被释放掉了,但是这个对象仍然可以被访问。因此,如果在这个对象内订阅了其他对象的事件,务必在 Dispose 方法中将事件退订 [5]。否则,事件发布者再次触发事件时,这个已经释放掉资源的对象还是会响应该事件,如果在事件响应方法中尝试访问已经释放的资源,则会发生意料外的错误。
WinForm 控件
从父容器中移除控件
当我们从一个容器中将某个控件 Remove 掉,这个控件的句柄并不会被释放,如果我们忘记显示地调用该控件的 Dispose 方法,又频繁地创建、移除控件,很快就会因为句柄过多而发生异常,面对这种情况,往往一头雾水,很难找到发生异常的根本原因。在以往的工作中,我一般会选择将一个控件从容器中 Remove 之后,再调用该控件的 Dispose 方法。后来无意间发现直接调用控件的 Dispose 方法,它会自动将自己从父容器中移除,通过阅读源码 [6] 证实了这个特性,真的挺方便。
移除自己的子控件
上面提到调用一个控件的 Dispose 方法,会自动将自己从父容器中移除。那么 Dispose 方法会对自己的子控件产生什么影响呢?是否需要在调用 Dispose 之前,先遍历并释放所有子控件呢?答案是不用,控件会自动调用所有子控件的 Dispose 方法,通过源码 [7] 可以证实这一点。可见,控件的 Dispose 方法是没有副作用的,一旦调用,就可以带着自己的资源,消失在我们的系统中,这种实现的思路,值得我们借鉴学习。
结语
IDisposable 为我们提供了便利,弥补了自动垃圾回收的不足,掌握好这个接口,不仅可以使我们的开发水平更进一步,也可以让我们的产品稳定性更上层楼。
参考文献:
[1] IDisposable 接口 (https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.7.2#definition)
[2] 清理非托管资源 (https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/unmanaged?view=netframework-4.7.2)
[3] Dispose 模式 (https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern)
[4] using 语法糖 (https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.7.2#the-c-and-visual-basic-using-statement)
[5] 取消订阅 (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events#unsubscribing)
[6] 从父容器中移除 (https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,6013)
[7] 自动释放子控件的资源 (https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,6017)