C#基础:Dispose()、Close()、Finalize()的区别详解

.net内存回收与Dispose﹐Close﹐Finalize方法
一. net的对象使用一般分为三种情况﹕
1.创建对象
2.使用对象
3.释放对象
二.创建对象
1.创建对象实际分为两个步骤﹕变量类型宣告和初始化对象
2.变量类型宣告(declare),如﹕


代码如下:

FileStream fs

这行代码会在当前的变量作用域空间(栈或堆)里建立一个叫做fs的变量﹐至少四个字节吧(因为要存一个对象的地址)
3.初始化对象
对象在使用(调用其方法或属性)前﹐必须进行初始化。
如﹕


代码如下:

fs = new FileStream(@"C:\test.txt",FileMode.OpenOrCreate);

这行代码会分成3个步骤﹕
a.在托管堆中分配一块内存﹐其大小等于FileStream中所有字段(当然不包括静态的)的内存总和加上MS认为需要的其它东东。
b.初始化对象的字段(值类型的把其位全部初始化成0,对象初始化为null﹐当然string是一个例外﹐它被初始化成空字符串)
c.调用FileStream相应的构造器﹐这里会初始化一个非托管资源(文件)的私有字段。
三.使用对象
使用对象就没什么讲的﹐就是调用对象的方法(或属性等)来完成某个功能当然为了释放对象而调用的方法其范畴应不属于此类中(现在提到的Finalize等)
四.释放对象
1.释放对象也就是说这个对象我已经不需要了﹐现在我要把其释放﹐以便把其在堆上所占用的内存空间给收回来(当然变量名的内存空间就不需要管了﹐因为它会随其作用域自动消失)
2. .net自动进行内存管理﹐也就是说当它判断一个对象没有用了(当然有自己的算法)﹐它就会将其内存给自动收回来﹐但是其收回的时间一般不确定(当.net认为内存紧张时﹐它就会开始)
BTW:其实我们就是想自己收回对象的内存也不可能﹐因为MS没有提供途径(GC.Collect也是启动.net的内存收集功能)
五.第一个结论
在net中使用对象很简单﹐创建对象之后直接使用就可以了﹐不用了也不要去管它﹐垃圾收集器会帮你把内存要回来的。
六.例外
当对象的成员引用了一个非托管资源时(不在托管堆上分配的内存或资源﹐像文件﹐数据库连接等等)﹐下面以一个例子来说明﹕
System.IO.FileStream类别﹐这是.net基本类库提供的一个非托管资源(文件)封装对象(用Reflector工具反编译mscorlib.dll可见其代码)
1.FileStream毫无疑问封装了一个非托管资源
观其源代码发现有这样一个私有成员﹕


代码如下:

private SafeFileHandle _handle;

通过构造器调用的Init方法可以发现这个成员的初始化代码﹕


代码如下:

this._handle = Win32Native.SafeCreateFile(text2, num1, share, secAttrs, mode, num2, 
Win32Native.NULL);

而后者实际上就是kernel32.dll中的CreateFile方法﹐它返回一个HANDLE(即非托管资源引用)
2.我们先来使用这个类别﹕


代码如下:

using System;
using System.IO;
public class TestFileStream
{
    public static void Main(string[] args)
    {   
           //创建一个FileStream对象
        FileStream fs = new FileStream(@"C:\test.txt",FileMode.OpenOrCreate);       
        Console.WriteLine("您可以尝试在系统中删除c盘下的test.txt(回车键继续)");
        //暂停程序执行﹐并尝试在系统中删除那个文件
        Console.ReadLine();

//删除文件测试
        try
        {
            File.Delete(@"c:\test.txt");
        }
        catch (IOException ex)
        {
            Console.WriteLine("[Error]程序删除文件失败﹕{0}",ex.Message);
        }
    }
}

3.在程序挂起时(Console.ReadLine等待输入)﹐删除文件会失败﹐很容易理解﹐因为文件打开后没有将其关闭﹐系统不知道这个文件是否还有用﹐所以帮我们保护这个文件(理所当然﹐那个非托管资源所使用的内存还被程序占用着)
4.但是在程序执行完后﹐我们再尝试删除文件﹐成功﹗为什么?(fs不是没有关闭那个SafeFileHandle吗?)
当然您可以说﹐windows操作系统在一个进程结束后会自动回收其资源﹐没错(但是如果是com就惨了﹐因为com是存在于自己的独立进程内﹐而操作系统不负责这个:(   )﹐不过这里不是因为windows操作系统的功能﹐而是.net垃圾收集器帮的忙。
5.看下面这个例子


代码如下:

using System;
using System.IO;
public class TestFileStream
{
    public static void Main(string[] args)
    {
        //创建一个FileStream对象
        FileStream fs = new FileStream(@"C:\test.txt", FileMode.OpenOrCreate);
        Console.WriteLine("您可以尝试在系统中删除c盘下的test.txt(回车键继续)");
       //暂停程序执行﹐并尝试在系统中删除那个文件
        Console.ReadLine();

/**//*进行垃圾收集*/
        GC.Collect();
        Console.WriteLine("再删一下试试");
        Console.ReadLine();
    }
}

6.注意中间那行代码:


代码如下:

GC.Collect();

这是强制要.net垃圾收集器进行垃圾收集。
我们再去尝试删除test.txt﹐居然可以被删除了﹐为什么呀?(fs不是没有关闭那个SafeFileHandle吗?)﹐让我细细道来﹕
7.我们首先了解一下.net垃圾收集器进行垃圾收集的四种时机(参见﹕.net框架程序设计 李建忠译)
a.最常见的﹕当.net觉得合适时﹐例如它感到内存紧张了(朮语称为﹕0代对象充满)
b.微软强烈不建议使用的﹕GC的Collect方法调用(就是我们上面用的这种啦﹐因为会降低性能﹐会挂起进程, 等等﹐反正听微软的吧。当然某些时候可以用﹐就像我上面用来测试的代码﹐呵呵...)
c.应用程序域卸载时(AppDomain)
d.CLR被关闭时
8.现在我们可以明白第1个例子为什么在程序结束后文件可以被删除﹐因为CLR被关闭时﹐.net执行了垃圾收集(也就是等同于第二个例子的GC.Collect()代码)
9.所以现在所有的问题都集中到垃圾收集上面﹐它做了什么?
a.垃圾收集器在判断一个对象不会再被引用到后﹐就开始对它进行垃圾收集(即回收内存)
b.清空内存(即把托管堆中的内存收回来)
c.但是对象的有些字段引用到了非托管资源怎么办?如FileStream的_handle
d.所以我们必须告诉垃圾收集器﹐在你回收我的内存之前﹐先帮我执行一个方法来收回我的非托管资源﹐以免托管堆的内存被你回收了﹐而我引用的非托管资源的内存却被泄漏了。
e.这个方法就是Finalize()﹐也就是C#的 ~ClassName() 方法(同C++中的析构语法)
f.所以一个对象如果存在Finalize方法时﹐垃圾收集器在收回它的内存之前就会自动调用这个方法
g.这样我们就可以把那些东东(非托管资源)给清理干净了
由此看来﹐垃圾收集器提供的这种机制就是为了更好的完善.net的自动内存管理的功能﹐让我们也可以参与到垃圾收集中去
10.我们再来看看GC.Collect()这行代码或CLR关闭时.Net做了什么﹕
a.垃圾收集器启动﹐发现fs引用的那个对象已经没用了(当然CLR关闭时才不管你有没有用﹐通通回收)﹐于是对它进行内存回收
b.发现fs的类型﹕FileStream提供了Finalize方法﹐于是先调用这个方法
(以下通过Reflector继续)
c.Finalize方法中有 this._handle.Dispose()代码﹐于是调用SafeHandler.Dispose()
d.接着转到(当然好多个圈﹐您悠着点...)SafeFileHandle.ReleaseHandle方法﹐发现代码﹕Win32Native.CloseHandle() (即关闭非托管资源--文件HANDLE)
真相大白﹕原来是垃圾收集器帮我们关闭了那个非托管资源(当然还是通过我们自己写的Finalize方法)﹐因此后面就可以删除文件了。
11.有人会问﹕好像我们平时在使用FileStream对象时﹐没这么复杂呀?
答﹕Very Good!
一部分人﹕是因为大家都和我的例1一样有好运气﹐那个C盘下的test.txt文件自从被创建后﹐我压根就不会再去用它﹐管它这部分资源有没有被泄漏﹐有没有被锁定﹐最后程序结束时﹐被垃圾收集器帮了忙﹐把忘了关闭的文件HANDLE给收回来了。
剩下的一部分人﹕在程序里埋下了一颗"哑弹"﹐不知什么时候会爆炸﹐就像我例子中的File.Delete方法就出现了异常。
(不过我觉得)绝大多数人﹕是在看了很多诸如.net编程忠告﹐Microsoft强烈建议﹐MSDN标准做法等等等等( 还有我这篇blog﹐呵呵)之后﹐知道了在使用如FileStream,SqlConnection这些东东时﹐必须将其Close。
12.Close与Dispose
查看我们那两个例子的代码﹐都是不标准的﹐正确做法应该在使用完那个FileStream后﹐调用fs.Close()将其关闭﹐以保证资源的安全。
附﹕正确做法


代码如下:

using System;
using System.IO;
public class TestFileStream
{
    public static void Main(string[] args)
    {
        //创建一个FileStream对象
        FileStream fs = new FileStream(@"C:\test.txt", FileMode.OpenOrCreate);
        /**//*在用完FileStream后关闭*/
        fs.Close();

//删除文件测试
        try
        {
            File.Delete(@"c:\test.txt");
        }
        catch (IOException ex)
        {
            Console.WriteLine("[Error]程序删除文件失败﹕{0}", ex.Message);
        }
    }
}

13.有人举手﹐讲这么多﹐早告诉我调用fs.Close不就得了。
哥们﹐fs.Close()方法是由您写的﹐调不调用﹐手在您身上﹐您不调用的话﹐哪天程序出了问题﹐您有会叫﹕微软真垃圾﹐.net真不稳定﹐还是java好﹐安全﹐可靠...    为防您的国骂﹐MS只好在垃圾收集中加这一款﹐以防不测...
14.Dispose模式
认真查看.net类库中的那些基本类别﹐凡是有Finalize方法的类别﹐基本上都提供了诸如Dispose,Close,Dispose(bool)等方法(FileStream也不例外)
15.其实不管是Dispose,Close,Finalize方法﹐最终应该都是执行相同的代码
区别﹕
Finalize方法﹕只能由微软调用
Dispose和Close方法﹕提供给您调用
因此在您使用完那些类别后﹐那就直接调用Close吧(没有Close﹐再调用Dispose方法)﹐当然万一您忘了﹐也别担心﹐还有垃圾收集器帮您垫后。
七.第二个结论﹕
1.在您开发一个封装非托管资源(即类中的字段引用到了非托管资源)的类别时﹕
A:强烈建议您提供Finalize方法进行非托管资源的释放﹐.net垃圾收集器不会帮您自动回收那部分资源﹐而是通过调用您的Finalize方法来帮您释放。(这样可以保证﹕在使用您类别的那位程序员忘了手动回收内存时﹐还可通过垃圾收集器来补救)
B.强烈建议您提供一个Close或Dispose方法﹐以便使用您类别的程序员可以手动释放您的类别中的非托管资源。(参见.net框架程序设计 自动内存管理一章实现Dispose模式)
C.如果类别封装了像FileStream这样的对象(即对非托管资源的再次封装)时﹐一般也应该提供一 个Close或Dispose方法﹐除非您的这个成员保证在每次使用后﹐都被正常的关闭﹐即对调用者透明。
2.在您使用一个封装非托管资源的类别时﹕
A:强烈建议您在明确知道这个类别没有用之后﹐调用其提供的Close或Dispose方法手动释放其非托管资源的 内存。有道是﹕有借有还﹐再借不难;借了不还﹐再借休想~~
B:注意在手动释放后﹐不要再调用该对象的相关方法了﹐因为对象已经损毁了
再次BTW:不管是Finalize﹐Close还是Dispose﹐您都无法显式释放托管堆内存﹐它们永远是微软的"私人财产 "﹕)
总结:Dispose 和 Close基本上是一样的。 Close 是为那些不熟悉Dispose的开发者设计的,Close让人更容易理解是做什么的。
在.net framework 里面,close()被设计成public的,并且在close()里面调用被隐藏的dispose(),而后dispose()再去调用另一个virtual的dispose(bool)
函数。所以如果从这个class继承,你就必须实现dispose(bool)方法 。调用者通过close()就会间接调用到你重载的那个dispose(bool)方法去释放资源。
因为close()只是用来呼叫那个隐藏的dispose()继而呼叫dispose(bool)的,用户不应该改变close行为的
具有disponse()方法的类是实现 了IDisposable 接口的。 .net中有很多class 只提供close(),而不对外提供disponse(),但是它的确实现了idisponse接口,
这是为什么呢?
究其原因是因为接口的实现模式 ---- 显式实现 隐式实现,二者的区别:对于 隐式实现 来说,你只需要调用 “new ClassA().Dispose()“,但是对于显式实现
来说,dispose()不会是这个 classA 的成员函数。唯一的调用方式是先强制类型转换到 IDisposable ,即"new ClassA().Dispose()",但是((IDisposable)new ClassA()).Dispose() 可以编译过。这样就符合了设计的要求:提供 close(),隐藏dispose(),并且实现 IDisposeable接口

(0)

相关推荐

  • C#基础教程之IComparable用法,实现List<T>.sort()排序

    List<T>.sort()可以实现对T的排序,比如List<int>.sort()执行后集合会按照int从小到大排序.如果T是一个自定义的Object,可是我们想按照自己的方式来排序,那该怎么办呢,其实可以用过IComparable接口重写CompareTo方法来实现.流程如下: 一.第一步我们申明一个类Person但是要继承IComparable接口: 复制代码 代码如下: using System; using System.Collections.Generic; usin

  • c#数据类型基础

    1.值类型 值类型包括简单值类型和复合型类型.简单值类型可以再细分为整数类型.字符类型.实数类型和布尔类型:而复合类型则是简单类型的复合,包括结构(struct)类型和枚举(enum)类型. 整数类型 数据类型 说明 取值范围 对应于System程序集中的结构 sbyte 有符号8位整数 -128-127 SByte byte 无符号8位整数 0-255 Byte short 有符号16位整数 -32768-32767 Int16 ushort 无符号16位整数 0-65535 UInt16 I

  • c#基础学习之封装

    作为一个初级GIS程序员,关于封装那些宏观的概念暂且不提,编程经常面对的就是"字段,属性,方法",这也是面向对象的基本概念之一. 1.字段 通常定义为private,表示类的状态信息 private string name; 2.属性 通常定义为public,表示类的对外成员.属性具有可读,可写,通过get和set访问器来实现其读写控制.如果属性为只读,则只实现get访问器即可:如果属性为可写,则实现set访问器即可.另外还有一种含参属性,在c#中称为索引器.索引器一般是用来方便对类

  • C#学习笔记——基本语法

    基本语法 C#,又名Csharp,天朝喜欢叫C井. C#是一种面向对象的编程语言.在面向对象的程序设计方法中,程序有各种相互交互的对象组成.相同种类的对象通常具有相同的类型,或者说,是在先沟通那个的class中. 例如,以Rectangle(矩形)对象为例,它具有length和width属性.根据设计,它可能需要接受这些属性值,计算面积和详细细节. 让我们来看看一个Rectangle类的实现,并借此套论C#的基本语法 using System; namespace RectangleApplic

  • c# 接口interface基础入门小例子

    复制代码 代码如下: /// <summary>    /// interface    /// 与抽象类的区别:    /// 1,abstract可以有具体方法和抽象方法(必须有一个抽象方法),interface没有方法实现    /// 2,abstract可以有构造函数和析构函数,接口不行    /// 3,一个类可以实现多个interface,但只能继承一个abstract    /// 特点:    /// interface成员隐式具有public,所以不加修饰符    ///

  • c#多线程编程基础

    无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应用户操作,同时在用户事件之间或者甚至在用户事件期间利用处理器,最强大的方式之一是使用多线程技术. 多线程:线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.如果某个线程进行一次长延迟操作, 处理器就切换到另一个线程执行.这样,多个线程的并行(并发)执行隐藏了长延迟,提高了处理器资源利用率

  • C#基础:Equals()与运算符==的区别分析

    对于值类型,如果对象的值相等,则相等运算符 (==) 返回 true,否则返回 false.对于string 以外的引用类型,如果两个对象引用同一个对象,则 == 返回 true.对于 string 类型,== 比较字符串的值.==操作比较的是两个变量的值是否相等.equals()方法比较的是两个对象的内容是否一致.equals也就是比较引用类型是否是对同一个对象的引用.对于值类型的比较,这里就不做描述了,下面讨论引用类型的比较:首先我们看一段程序 复制代码 代码如下: using System

  • C#零基础学习理解委托

    说来惭愧,在大学的课程中,竟然没有听说过委托这个名称.那么今天我就带着大家一起探讨下委托和事件. 咱们先来看下委托 我主要从以下几个方面讲解 1,  为什么使用委托  2.什么是委托  3.委托如何使用 为什么使用委托? 委托是c#中非常重要的一个概念,使用委托使程序员可以将方法引用封装在委托对象内.然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法.与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的. 什么是委托? 委托是一种引用方法的类型,一旦为委

  • C#基础:Dispose()、Close()、Finalize()的区别详解

    .net内存回收与Dispose﹐Close﹐Finalize方法一. net的对象使用一般分为三种情况﹕1.创建对象2.使用对象3.释放对象二.创建对象1.创建对象实际分为两个步骤﹕变量类型宣告和初始化对象2.变量类型宣告(declare),如﹕ 复制代码 代码如下: FileStream fs 这行代码会在当前的变量作用域空间(栈或堆)里建立一个叫做fs的变量﹐至少四个字节吧(因为要存一个对象的地址)3.初始化对象对象在使用(调用其方法或属性)前﹐必须进行初始化.如﹕ 复制代码 代码如下:

  • python基础之//、/与%的区别详解

    目录 示例代码如下: 附:一分钟看懂Python中的 // 和 / 和 % 的用法区别 总结 “ // ” 表示整数除法,返回整数 比如 7/3 结果为2 “ / ” 表示浮点数除法,返回浮点数 (即小数) 比如 8/2 结果为4.0 “ %” 表示取余数 比如7/4 结果为3 示例代码如下: pycharm环境下可直接运行使用 a = 321 b = a//100 c = a//10 % 10 d = a % 10 print("百位数是%d" % b) print("十位

  • IOS 基础之nil,NULL,NSNULL区别详解

    IOS 基础之nil,NULL,NSNULL区别详解 ① nil:一般赋值给空对象. ② NULL:NULL 是一个通用指针(泛型指针). 一般赋值给 nil 之外的其他空值.如SEL等. ③ NSNULL:[NSNull null] 是一个对象,他用在不能使用 nil 的场合. 因为在 NSArray 和 NSDictionary 中 nil 有特殊的含义(表示列表结束),所以不能在集合中放入 nil 值.如要确实需要存储一个表示"什么都没有"的值,可以使用 NSNull 类.NSN

  • JavaScript里 ==与===区别详解

    1.对于string,number等基础类型,==和===是有区别的 1)不同类型间比较,==之比较"转化成同一类型后的值"看"值"是否相等,===如果类型不同,其结果就是不等 2)同类型比较,直接进行"值"比较,两者结果一样 2.对于Array,Object等高级类型,==和===是没有区别的 进行"指针地址"比较 3.基础类型与高级类型,==和===是有区别的 1)对于==,将高级转化为基础类型,进行"值&quo

  • Python探索之静态方法和类方法的区别详解

    面相对象程序设计中,类方法和静态方法是经常用到的两个术语. 逻辑上讲:类方法是只能由类名调用:静态方法可以由类名或对象名进行调用. python staticmethod and classmethod Though classmethod and staticmethod are quite similar, there's a slight difference in usage for both entities: classmethod must have a reference to

  • js监听键盘事件的方法_原生和jquery的区别详解

    经常需要监听键盘的事件,以便做更好的操作,基本原理是:监听全局键盘,每一个键盘,当用户按下某一按键时,返回对应的键值,然后再判断用户按下了哪一科按键,键值对应按键的名称在最下面列出,自行比对.去以下介绍两种不同的方式 原生键盘监听事件:按下一次按键,分为三个过程,按下-按住-松开 onkeydown:某个键被按下 onkeypress:某个键盘的键被按下或按住 onkeyup:某个键盘的键被松开 使用方法,一般来说,键盘监听是直接在整个页面文档上进行监听的,也就是document上 以下是原生的

  • java 中同步方法和同步代码块的区别详解

    java 中同步方法和同步代码块的区别详解 在Java语言中,每一个对象有一把锁.线程可以使用synchronized关键字来获取对象上的锁.synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁). 问题的由来: 看到这样一个面试题: //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized (obj){} } synchronized用于解决同步问

  • C#多线程与异步的区别详解

    C#多线程与异步的区别详解 随着拥有多个硬线程 CPU(超线程.双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论.本文主要是想与各位高手一同探讨一下如何使用并发来最大化程序的性能. 多线程和异步操作的异同 多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性.甚至有些时候我们就认为多线程和异步操作是等同的概念.但是,多线程和异步操作还是有一些区别的.而这些区别造成了使用多线程和异步操作的时机的区别. 异步操作的本质 所有的程序最终都会由计算机硬件来

  • javaScript中"=="和"==="的区别详解

    区别: ==, 两边值类型不同的时候,要先进行类型转换,再比较. ==,不做类型转换,类型不同的一定不等. 下面分别说明: 先说 "===",这个比较简单.下面的规则用来判断两个值是否===相等: 1.如果类型不同,就不相等 2.如果两个都是数值,并且是同一个值,那么[相等]:(!例外)的是,如果其中至少一个是NaN,那么[不相等].(判断一个值是否是NaN,只能用isNaN()来判断) 3.如果两个都是字符串,每个位置的字符都一样,那么相等:否则不相等 . 4.如果两个值都是true

  • vue安装和使用scss及sass与scss的区别详解

    1. 安装依赖:npm install node-sass sass-loader -D 2. webpack.base.conf.js文件 module: { { //手动添加这一条,相当于是编译识别sass! test: /\.scss$/, loaders: ["style", "css", "sass"]} } 3. 在.vue文件中使用 <style scoped lang="scss"> .box{ w

随机推荐