一、背景

最近在精读 《CLR Via C#》和 《Effective C#》 的时候,发现的一个问题点。一般来说,我们实现 IDisposable 接口,是为了释放托管资源和非托管资源。不过在 C# 类型定义里面有一个功能类似的东西,那就是 终结器

最开始我是学 C++ 的,之后学 C# 的时候发现这玩意儿不论是写法和作用,都跟 C++ 里面的 析构函数 一样。在 C++ 里面的析构函数是在对象释放的时候会被调用,之后这个观点一直被我带到 C#,认为资源释放的动作放在终结器不就行了么。为什么还要我实现 IDisposable 接口,然后让使用者手动释放呢?

C++ 版本的析构函数:

class Line
{
public:
Line();
~Line(); private:
double length;
};

C# 版本的终结器:

public class Line
{
private double _length; public Line()
{ } ~Line()
{ }
}

二、原因

说起这个原因,首先得从 C# 终结器的 调用时机 说起。终结器的调用是 CLR 在进行 GC 时,如果某个对象写有终结器,即便它应该被释放,也不会马上回收该对象。而 C++ 的析构函数是确定性析构,取决于你调用 delete 的时机。

GC 会将其添加到一个队列当中,单独使用了一个 高优先级 线程去调用对象的终结器。因为要保证线程能够访问到终结器对象,所以本该释放的对象,以及对象相关的资源就 会被提升 1 代 ,会 增加内存占用

一旦终结器方法带有死循环,那么 GC 将永远无法释放该资源,造成 内存泄漏

除开内存占用增大的原因,如果你在终结器方法内部引用了其他带终结器对象,GC 无法保证终结器调用顺序,所以你可能访问到的对象是已经终结了的。

还有一种情况会导致尴尬的内存泄漏,本来对象 A 应该被释放了,结果你在终结器内部又让其他的根保持对象的引用,又会让这个对象复活。因为 GC 只会执行一次带终结器对象的终结器。执行一次过后,就再也不会执行对象的终结器了。

public class BadClass
{
private static readonly List<BadClass> _list = new List<BadClass>();
private string _msg; public BadClass(string msg)
{
_msg = (string)msg.Clone();
} ~BadClass()
{
// 造成 _msg 的内存不会被释放。
_list.Add(this);
}
}

三、最佳实践

针对 Effective C# 所提出的最佳实践,你应该为对象实现 IDisposable 接口,以释放托管资源。如果你对象确实使用了非托管资源,那么你也应该为其编写终结器。因为非托管资源的,你不能保证调用者能够显示调用 Dispose() 方法,所以你得通过终结器来处理。

一个典型的 Dispose() 方法应该将托管资源、非托管资源全部进行释放,设置对应的标识表明对象已经被释放了,阻止垃圾回收器重复清理该对象、保证方法的 幂等性

public class FatherClass : IDisposable
{
private bool isDisposed = false; public void Dispose()
{
Dispose(true);
// 通知 GC,这个对象已经完全被清理。
GC.SuppressFinalize(this);
} ~FatherClass()
{
Dispose(false);
} protected virtual Dispose(bool isDisposing)
{
if(isDisposed) return; if(isDisposing)
{
// 释放托管资源。
} // 释放非托管资源。
isDisposed = true;
} public void TestMethod()
{
if(isDisposed)
{
throw new ObjectDisposedException("对象已经被释放。");
}
}
} public class ChildClass : FatherClass
{
private bool isDisposed = false; protected override void Dispose(bool isDisposing)
{
if(isDisposed) return; if(isDisposing)
{
// 释放托管资源。
} base.Dispose(isDisposing); isDisposed = true;
}
}

在上面的实践中,我们提炼出了一个 void Dispose(bool) 方法,并将其设置为虚函数。这样做的好处有两点,第一点是方便子类重写释放逻辑,第二点是可以将终结器和 Dispose() 方法内部重复的代码提炼出来。

为什么要实现 IDisposable 接口?的更多相关文章

  1. C#中对IDisposable接口的理解

    http://blog.sina.com.cn/s/blog_8abeac5b01019u19.html C#中对IDisposable接口的理解 本人最近接触一个项目,在这个项目里面看到很多类实现了 ...

  2. IDisposable接口

    C#中IDisposable接口的主要用途是释放非托管资源.当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存.但无法预测进行垃圾回收的时间.另外,垃圾回收器对窗口句柄或打开的文件和流等非托 ...

  3. IDisposable 接口2

    定义一种释放分配的资源的方法. 命名空间:  System程序集:  mscorlib(在 mscorlib.dll 中) 语法 C# C++ F# VB [ComVisibleAttribute(t ...

  4. IDisposable接口详解

    转载:http://www.cnblogs.com/davyli/archive/2010/09/13/1825042.html 正确实现 IDisposable .NET中用于释放对象资源的接口是I ...

  5. .NET中IDisposable接口的基本使用

    首先来看MSDN中关于这个接口的说明: [ComVisible(true)] public interface IDisposable { // Methods void Dispose(); } 1 ...

  6. 利用IDisposable接口构建包含非托管资源对象

    托管资源与非托管资源 在.net中,对象使用的资源分为两种:托管资源与非托管资源.托管资源由CLR进行管理,不需要开发人员去人工进行控制,.NET中托管资源主要指"对象在堆中的内存" ...

  7. 深入理解C#中的IDisposable接口

    写在前面 在开始之前,我们需要明确什么是C#(或者说.NET)中的资源,打码的时候我们经常说释放资源,那么到底什么是资源,简单来讲,C#中的每一种类型都是一种资源,而资源又分为托管资源和非托管资源,那 ...

  8. C#中的IDisposable接口

    深入理解C#中的IDisposable接口 写在前面 在开始之前,我们需要明确什么是C#(或者说.NET)中的资源,打码的时候我们经常说释放资源,那么到底什么是资源,简单来讲,C#中的每一种类型都是一 ...

  9. 【转】C#中对IDisposable接口的理解

    IDisposable接口定义:定义一种释放分配的资源的方法. .NET 平台在内存管理方面提供了GC(Garbage Collection),负责自动释放托管资源和内存回收的工作,但它无法对非托管资 ...

  10. 【转】C# 的 IDisposable 接口

    C# 的 IDisposable 接口 我在微软的团队快被微软 C# 里面的各种 IDisposable 对象给折腾疯了…… 故事比较长,先来科普一下.如果你没有用过 C#,IDisposable 是 ...

随机推荐

  1. Linux中新建用户用不了sudo命令问题:rootr is not in the sudoers file.This incident will be reported解决

    参考:https://blog.csdn.net/lichangzai/article/details/39501025 如果执行sudo命令的用户没有执行sudo的权限,执行sudo命令时会报下面的 ...

  2. [币严区块链]ETH搭建节点区块数据同步的三种模式:full、fast、light

    ETH  全节点Archive(归档)模式数据量增长图 上述图表可通过链接查看:https://etherscan.io/chartsync/chainarchive 通过上表,可以看到截止2019年 ...

  3. 四大组件初始之ContentProvider

    在android中,除了存放在外部存储的共享目录下的数据,各个应用的数据库文件,资源等都是私有的,其他应用没有访问权限.所以有了ContentProvider,不包含功能逻辑,用于不同应用进程间共享数 ...

  4. ASP.NET Core 2.2 : 二十一. 内容协商与自定义IActionResult和格式化类

    上一章的结尾留下了一个问题:同样是ObjectResult,在执行的时候又是如何被转换成string和JSON两种格式的呢? 本章来解答这个问题,这里涉及到一个名词:“内容协商”.除了这个,本章将通过 ...

  5. .Net基础篇_学习笔记_第七天_计算质数(找出0-100以内说有质数)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  6. linux ssh连接自动断开问题

    场景描述:云上的虚拟机使用public ip连接ssh时,一直提示已经连接,但是就会自动关闭 1. 通过正常虚拟机作为跳板,能够连接到目标机子上,检查发现进程正常,但是就一直连接不上 [root@te ...

  7. JDBC连接时出现的问题总结

    java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more tha ...

  8. [VB.NET Tips]字符串转换为日期

    有些字符串需要转换成日期,或者整型转换为日期,可以参考如下思路: Dim result As Date Dim source As String = "20190515" resu ...

  9. json入门初体验

    json是JavaScript对象表示法,也是轻量级的文本数据交互格式,独立于语言,能够自我描述 json文本格式在语法上与创建JavaScript对象代码相同,多以json不需要解析器,js程序能够 ...

  10. JS思考遍历对象

    var json={ “name”:”小明”, “age”:”10”, “sex”:”男” } for(var key in json); //key只是个变量名 console.log(key); ...