在开始之前,我们需要明确什么是C#(或者说.NET)中的资源,打码的时候我们经常说释放资源,那么到底什么是资源,简单来讲,C#中的每一种类型都是一种资源,而资源又分为托管资源和非托管资源,那这又是什么?!
托管资源:由CLR管理分配和释放的资源,也就是我们直接new出来的对象;
非托管资源:不受CLR控制的资源,也就是不属于.NET本身的功能,往往是通过调用跨平台程序集(如C++)或者操作系统提供的一些接口,比如Windows内核对象、文件操作、数据库连接、socket、Win32API、网络等。
我们下文讨论的,主要也就是非托管资源的释放,而托管资源.NET的垃圾回收已经帮我们完成了。其实非托管资源有部分.NET的垃圾回收也帮我们实现了,那么如果要让.NET垃圾回收帮我们释放非托管资源,该如何去实现。
如何正确的显式释放资源
假设我们要使用FileStream,我们通常的做法是将其using起来,或者是更老式的try…catch…finally…这种做法,因为它的实现调用了非托管资源,所以我们必须用完之后要去显式释放它,如果不去释放它,那么可能就会造成内存泄漏。
这听上去貌似很简单,但我们编码的时候可能很多时候会忽略掉释放资源这个问题,.NET的垃圾回收又如何帮我们释放非托管资源,接下来我们一探究竟吧,一个标准的释放非托管资源的类应该去实现IDisposable接口:
public class MyClass:IDisposable
{
/// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary>
public void Dispose()
{
}
}
我们实例化的时候就可以将这个类using起来:
using(var mc = new MyClass())
{
}
看上去很简单嘛,但是,要是就这么简单的话,也没有这篇文章的必要了。如果要实现IDisposable接口,我们其实应该这样做:
- 实现Dispose方法;
- 提取一个受保护的Dispose虚方法,在该方法中实现具体的释放资源的逻辑;
- 添加析构函数;
- 添加一个私有的bool类型的字段,作为释放资源的标记
接下来,我们来实现这样的一个Dispose模式:
public class MyClass : IDisposable
{
/// <summary>
/// 模拟一个非托管资源
/// </summary>
private IntPtr NativeResource {
get; set; } = Marshal.AllocHGlobal(100);
/// <summary>
/// 模拟一个托管资源
/// </summary>
public Random ManagedResource {
get; set; } = new Random();
/// <summary>
/// 释放标记
/// </summary>
private bool disposed;
/// <summary>
/// 为了防止忘记显式的调用Dispose方法
/// </summary>
~MyClass()
{
//必须为false
Dispose(false);
}
/// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary>
public void Dispose()
{
//必须为true
Dispose(true);
//通知垃圾回收器不再调用终结器
GC.SuppressFinalize(this);
}
/// <summary>
/// 非必需的,只是为了更符合其他语言的规范,如C++、java
/// </summary>
public void Close()
{
Dispose();
}
/// <summary>
/// 非密封类可重写的Dispose方法,方便子类继承时可重写
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
//清理托管资源
if (disposing)
{
if (ManagedResource != null)
{
ManagedResource = null;
}
}
//清理非托管资源
if (NativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(