.Net中Finalize()和Dispose()有什么区别?

Finalize自动释放资源,Dispose()用于手动释放资源。

释放类所使用的未托管资源的两种方式:

1.利用运行库强制执行的析构函数,但析构函数的执行是不确定的,而且,由于垃圾收集器的工作方式,它会给运行库增加不可接受的系统开销。

2.IDisposable接口提供了一种机制,允许类的用户控制释放资源的时间,但需要确保执行Dispose()。

一般情况下,最好的方法是执行这两种机制,获得这两种机制的优点,克服其缺点。假定大多数程序员都能正确调用Dispose(),实现IDisposable接口,同时把析构函数作为一种安全的机制,以防没有调用Dispose()。

一. Finalize

  Finalize很像C++的析构函数,我们在代码中的实现形式为这与C++的析构函数在形式上完全一样,但它的调用过程却大不相同。

~ClassName() {//释放你的非托管资源}

  比如类A中实现了Finalize函数,在A的一个对象a被创建时(准确的说应该是构造函数被调用之前),它的指针被插入到一个 finalization链表中;在GC运行时,它将查找finalization链表中的对象指针,如果此时a已经是垃圾对象的话,它会被移入一个 freachable队列中,最后GC会调用一个高优先级线程,这个线程专门负责遍历freachable队列并调用队列中所有对象的Finalize方 法,至此,对象a中的非托管资源才得到了释放(当然前提是你正确实现了它的Finalize方法),而a所占用的内存资源则必需等到下一次GC才能得到释 放,所以一个实现了Finalize方法的对象必需等两次GC才能被完全释放。

  由于Finalize是由GC负责调用,所以可以说是一种自动的释放方式。但是这里面要注意两个问题:第一,由于无法确定GC何时会运作,因此 可能很长的一段时间里对象的资源都没有得到释放,这对于一些关键资源而言是非常要命的。第二,由于负责调用Finalize的线程并不保证各个对象的 Finalize的调用顺序,这可能会带来微妙的依赖性问题。如果你在对象a的Finalize中引用了对象b,而a和b两者都实现了Finalize, 那么如果b的Finalize先被调用的话,随后在调用a的Finalize时就会出现问题,因为它引用了一个已经被释放的资源。因此,在 Finalize方法中应该尽量避免引用其他实现了Finalize方法的对象。

  可见,这种“自动”释放资源的方法并不能满足我们的需要,因为我们不能显示的调用它(只能由GC调用),而且会产生依赖型问题。我们需要更准确的控制资源的释放。

二. Dispose

  Dispose是提供给我们显示调用的方法。由于对Dispose的实现很容易出现问题,所以在一些书籍上(如《Effective C#》和《Applied Microsoft.Net Framework Programming》)给出了一个特定的实现模式:

 class DisposePattern :IDisposable
    {
        private System.IO.FileStream fs = new System.IO.FileStream("test.txt", System.IO.FileMode.Create);
        ~DisposePattern()
        {
            Dispose(false);
        }      
        IDisposable Members#region IDisposable Members
        public void Dispose()
        {
            //告诉GC不需要再调用Finalize方法,
            //因为资源已经被显示清理
            GC.SuppressFinalize(this);
            Dispose(true);
        }
        #endregion
                protected virtual void Dispose(bool disposing)
        {
            //由于Dispose方法可能被多线程调用,
            //所以加锁以确保线程安全
            lock (this)
            {
                if (disposing)
                {
                    //说明对象的Finalize方法并没有被执行,
                    //在这里可以安全的引用其他实现了Finalize方法的对象
                }
                if (fs != null)
                {
                    fs.Dispose();
                    fs = null; //标识资源已经清理,避免多次释放
                }
            }
        }
    }

当然,如果DerivedClass本身没有什么资源需要清理,那么就不需要重写Dispose方法了,正如我们平时做的一些对话框,虽然都是继承 于System.Windows.Forms.Form,但我们常常不需要去重写基类Form的Dispose方法,因为本身没有什么非托管的咚咚需要释 放。

了解GC的脾性在很多时候是非常必要的,起码在出现资源泄漏问题的时候你不至于手足无措。我写过一个生成excel报表的控件,其中对excel对 象的释放就让我忙活了一阵。如果你做过excel开发的话,可能也遇到过结束excel进程之类的问题,特别是包装成一个供别人调用的库时,何时释放 excel对象以确保进程结束是一个关键问题。当然,GC的内部机制非常复杂,还有许多内容可挖,但了解所有细节的成本太高,只需了解基础,够用就好。

using() 语法有用吗?

using()能自动调用Dispose方法

比如:using()会自动调用MyObject的Dispose方法

using ( MyObject myObject = new MyObject ( ) )
{
   Console.WriteLine ( "quit" ) ;
}

IDisposiable是显示释放对象的接口,实现IDisposiable接口的类,可以显示的释放对象。

,通过编写Dispose方法来实现显式释放资源;

// C#
class MyClass : IDisposable
{
public MyClass() {} // 构造函数
~MyClass() {} // 析构方法 (不确定的) (编译器通过重载virtual void Finalize来实现),与C++/CLI的!MyClass()等效
public void Dispose() {} // Dispose方法
public static void Test()
{
using(MyClass auto = new MyClass())
{ /* 使用auto对象 */ }
// 因为使用了using句法,编译器自动调用auto.Dispose()
// 以上代码等效于:
MyClass user = new MyClass();
try { /* 使用user对象 */ }
finally { user.Dispose(); }
}
}

.Net中三种最常的释放资源方法如下:

  1. 析构函数;(由GC调用,不确定什么时候会调用)

  2. 继承IDisposable接口,实现Dispose方法;(可以手动调用。比如数据库的连接,SqlConnection.Dispose(),因为如果及时释放会影响数据库性能。这时候会用到这个,再如:文件的打开,如果不释放会影响其它操作,如删除操作。调用Dispose后这个对象就不能再用了,就等着被GC回收。)

  3. 提供Close方法。(类似Dispose但是,当调用完Close方法后,可以通过Open重新打开)

析构函数不能显示调用,而对于后两种方法来说,都需要进行显示调用才能被执行。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被重新进行使用;而Dispose方法来说,此对象所占有的资源需要被标记为无用了,也就是此对象要被销毁,不能再被使用。

析构函数

Dispose方法

Close方法

意义

销毁对象

销毁对象

关闭对象资源

调用方式

不能被显示调用,在GC回收是被调用

需要显示调用或者通过using语句

需要显示调用

调用时机

不确定

确定,在显示调用或者离开using程序块

确定,在显示调用时

Finalize()Dispose()Object 类的方法 属于Idisposable 接口执行速度慢 速度快执行时机是不确定性,它意味着当垃圾收集器调用finalize()方法来回收内存时。它是确定性函数,因为Dispose()方法被用户代码显式地调用。当您实现文件、数据库连接等非托管资源时,可以使用它释放这些在对象被销毁之前由对象持有的资源。它在任何时候都被用来释放非托管资源,如文件、数据库连接等。

Finalize()

Dispose()

Object 类的方法

属于Idisposable 接口

执行速度慢

速度快

执行时机是不确定性,它意味着当垃圾收集器调用finalize()方法来回收内存时。

它是确定性函数,因为Dispose()方法被用户代码显式地调用。

当您实现文件、数据库连接等非托管资源时,可以使用它释放这些在对象被销毁之前由对象持有的资源。

它在任何时候都被用来释放非托管资源,如文件、数据库连接等。

Finalize()

Dispose()

Object 类的方法

属于Idisposable 接口

执行速度慢

速度快

执行时机是不确定性,它意味着当垃圾收集器调用finalize()方法来回收内存时。

它是确定性函数,因为Dispose()方法被用户代码显式地调用。

当您实现文件、数据库连接等非托管资源时,可以使用它释放这些在对象被销毁之前由对象持有的资源。

它在任何时候都被用来释放非托管资源,如文件、数据库连接等。

原文发布于微信公众号 - 程序你好(codinghello)

原文发表时间:2018-06-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏皮皮之路

【JDK1.8】JUC——LockSupport

408170
来自专栏技术记录

通讯协议序列化解读(一) Protobuf详解教程

59170
来自专栏雨尘分享

5.Block的内存管理 内存管理内存管理block的循环引用

18340
来自专栏蓝天

TPL: 一个新的C++正则表达式(regex)库

C++ 中正则表达式(regex)库已经很多。光 boost 中就有3个:regex、spirit、xpressive。那么我们为什么还需要一个新的呢?

10110
来自专栏Java Web

Java 7的新特性

前言 看大佬推荐的书单买了一本《Java 8实战》,总觉得在了解Java 8之前,是不是也应该去了解了解一下Java 7的一些特性?所以就自己百度了一些资料来...

36550
来自专栏黑泽君的专栏

c语言基础学习11_项目实战:IDE(集成开发环境)

============================================================================= ==...

24820
来自专栏分布式系统进阶

Kafka中的时间轮Kafka源码分析-汇总

将TimerTask对象绑定到 TimerTaskEntry上 如果这个TimerTask对象之前已经绑定到了一个 TimerTaskEntry上, 先调用t...

24110
来自专栏Java Web

Java 面试知识点解析(四)——版本特性篇(1)

在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Jav...

51660
来自专栏PHP在线

PHP 底层的运行机制与原理

原文出处: nowamagic 欢迎分享原创到伯乐头条 PHP说简单,但是要精通也不是一件简单的事。我们除了会使用之外,还得知道它底层的工作原理。 PHP是...

47170
来自专栏IT技术精选文摘

JVM参数详解及OOM

JVM参数 堆的限制 JVM中最大堆大小有三方面限制: 相关操作系统的数据模型(32-bt还是64-bit)限制 系统的可用虚拟内存限制 系统的可用物理内存限制...

31360

扫码关注云+社区

领取腾讯云代金券