C#资源释放

转自:http://www.cnblogs.com/psunny/archive/2009/07/07/1518812.htmlhtml

深入理解C#中资源释放

      今天个人一个朋友看到我写的那篇《C#中用AJAX验证用户登陆》时,给我指出了点小毛病。就是在用户登陆时,若是用户登陆失败,在下面这段代码中,都会new出来一个User对象,若是连续登陆失败屡次,就会生成多个User对象,而它们在登陆失败后已经无用了,依然占据着内存,就算是C#有垃圾回收机制,但不肯定何时对这些对象进行回收。
而后去网上找了一篇C#资源释放的文章,讲的很透彻,和你们分享一下。程序员

Code

 

      首先,咱们须要明确2个概念。算法

 

第一个就是不少人用.Net写程序,会谈到托管这个概念。那么.Net所指的资源托管究竟是什么意思,是相对于全部资源,仍是只限于某一方面资源?不少人对此不是很了解,其实.Net所指的托管只是针对内存这一个方面,并非对于全部的资源;所以对于Stream,数据库的链接,GDI+的相关对象,还有Com对象等等,这些资源并非受到.Net管理而统称为非托管资源。而对于内存的释放和回收,系统提供了GC-Garbage Collector,而至于其余资源则须要手动进行释放。
 
那么第二个概念就是什么是垃圾,经过我之前的文章,会了解到.Net类型分为两大类,一个就是值类型,另外一个就是引用类型。前者是分配在栈上,并不须要GC回收;后者是分配在堆上,所以它的内存释放和回收须要经过GC来完成。GC的全称为“Garbage Collector”,顾名思义就是垃圾回收器,那么只有被称为垃圾的对象才能被GC回收。也就是说,一个引用类型对象所占用的内存须要被GC回收,须要先成为垃圾。那么.Net如何断定一个引用类型对象是垃圾呢,.Net的判断很简单,只要断定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。
 
明确了这两个基本概念,接下来讲说GC的运做方式以及其的功能。内存的释放和回收须要伴随着程序的运行,所以系统为GC安排了独立的线程。那么GC的工做大体是,查询内存中对象是否成为垃圾,而后对垃圾进行释放和回收。那么对于GC对于内存回收采起了必定的优先算法进行轮循回收内存资源。其次,对于内存中的垃圾分为两种,一种是须要调用对象的析构函数,另外一种是不须要调用的。GC对于前者的回收须要经过两步完成,第一步是调用对象的析构函数,第二步是回收内存,可是要注意这两步不是在GC一次轮循完成,即须要两次轮循;相对于后者,则只是回收内存而已。
 
很明显得知,对于某个具体的资源,没法确切知道,对象析构函数何时被调用,以及GC何时会去释放和回收它所占用的内存。那么对于从C、C++之类语言转换过来的程序员来讲,这里须要转变观念。
 
那么对于程序资源来讲,咱们应该作些什么,以及如何去作,才能使程序效率最高,同时占用资源能尽快的释放。前面也说了,资源分为两种,托管的内存资源,这是不须要咱们操心的,系统已经为咱们进行管理了;那么对于非托管的资源,这里再重申一下,就是Stream,数据库的链接,GDI+的相关对象,还有Com对象等等这些资源,须要咱们手动去释放。
 
如何去释放,应该把这些操做放到哪里比较好呢。.Net提供了三种方法,也是最多见的三种,大体以下:
<!--[if !supportLists]-->1. <!--[endif]-->析构函数;
<!--[if !supportLists]-->2. <!--[endif]-->继承IDisposable接口,实现Dispose方法;
<!--[if !supportLists]-->3. <!--[endif]-->提供Close方法。
 
通过前面的介绍,能够知道析构函数只能被GC来调用的,那么没法肯定它何时被调用,所以用它做为资源的释放并非很合理,由于资源释放不及时;可是为了防止资源泄漏,毕竟它会被GC调用,所以析构函数能够做为一个补救方法。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被从新进行使用;而Dispose方法来讲,此对象所占有的资源须要被标记为无用了,也就是此对象被销毁了,不能再被使用。例如,常见SqlConnection这个类,当调用完Close方法后,能够经过Open从新打开数据库链接,当完全不用这个对象了就能够调用Dispose方法来标记此对象无用,等待GC回收。明白了这两种方法的意思后,你们在往本身的类中添加的接口时候,不要歪曲了这二者意思。数据库

接下来讲说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使你们的印象更深。
首先是这三种方法的实现,大体以下:编程

Code

      对于Close来讲不属于真正意义上的释放,除了注意它须要显示被调用外,我在此对它很少说了。而对于析构函数而言,不是在对象离开做用域后马上被执行,只有在关闭进程或者调用GC.Collect方法的时候才被调用,参看以下的代码运行结果。函数

Code

运行的结果为:
After created!
Destructor called!post

 显然在出了Create函数外,myClass对象的析构函数没有被马上调用,而是等显示调用GC.Collect才被调用。
 
对于Dispose来讲,也须要显示的调用,可是对于继承了IDisposable的类型对象可使用using这个关键字,这样对象的Dispose方法在出了using范围后会被自动调用。例如:测试

     using ( DisposeClass myClass  =   new  DisposeClass() )
    
{
        
//other operation here
    }

如上运行的结果以下:
Dispose called!this

       那么对于如上DisposeClass类型的Dispose实现来讲,事实上GC还须要调用对象的析构函数,按照前面的GC流程来讲,GC对于须要调用析构函数的对象来讲,至少通过两个步骤,即首先调用对象的析构函数,其次回收内存。也就是说,按照上面所写的Dispose函数,虽然说被执行了,可是GC仍是须要执行析构函数,那么一个完整的Dispose函数,应该经过调用GC.SuppressFinalize(this )来告诉GC,让它不用再调用对象的析构函数中。那么改写后的DisposeClass以下:url

Code

      经过以下的代码进行测试。

Code

运行的结果以下:
Dispose called!
After Run!

 

显然对象的析构函数没有被调用。经过如上的实验以及文字说明,你们会获得以下的一个对比表格。
 
析构函数
Dispose 方法
Close 方法
意义
销毁对象
销毁对象
关闭对象资源
调用方式
不能被显示调用,会被GC 调用
须要显示调用
或者经过using 语句
须要显示调用
调用时机
不肯定
肯定,在显示调用或者离开using 程序块
肯定,在显示调用时
 
那么在定义一个类型的时候,是否必定要给出这三个函数地实现呢。
 
个人建议大体以下。
<!--[if !supportLists]--> 1.<!--[endif]--> 提供析构函数,避免资源未被释放,主要是指非内存资源;
<!--[if !supportLists]--> 2.<!--[endif]--> 对于Dispose Close 方法来讲,须要看所定义的类型所使用的资源(参看前面所说),而决定是否去定义这两个函数;
<!--[if !supportLists]--> 3.<!--[endif]--> 在实现Dispose 方法的时候,必定要加上“ GC.SuppressFinalize( this ) ”语句,避免再让GC 调用对象的析构函数。
 
C#程序所使用的内存是受托管的,但不意味着滥用,好地编程习惯有利于提升代码的质量以及程序的运行效率。
相关文章
相关标签/搜索