C# 资源释放

C# 资源释放程序员

终于开始动手写这篇文章了,有个网友催了我好几回,今天终于能够静下心来完成它。
 
便于对文章的开展,须要先明确两个概念。
第一个就是不少人用.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何时会去释放和回收它所占用的内存。那么对于从 CC++之类语言转换过来的程序员来讲,这里须要转变观念。
 
那么对于程序资源来讲,咱们应该作些什么,以及如何去作,才能使程序效率最高,同时占用资源能尽快的释放。前面也说了,资源分为两种,托管的内存资源,这是不须要咱们操心的,系统已经为咱们进行管理了;那么对于非托管的资源,这里再重申一下,就是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回收。明白了这两种方法的意思后,你们在往本身的类中添加的接口时候,不要歪曲了这二者意思。
 
接下来讲说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使你们的印象更深。
首先是这三种方法的实现,大体以下:
    ///<summary>
    /// The class to show three disposal function
    ///</summary>
    public class DisposeClass:IDisposable
    {
        public void Close()
        {
            Debug.WriteLine( "Close called!" );
        }
 
        ~DisposeClass()
        {
            Debug.WriteLine( "Destructor called!" );
        }
 
        #region IDisposable Members
 
        public void Dispose()
        {
            // TODO: Add DisposeClass.Dispose implementation
            Debug.WriteLine( "Dispose called!" );
        }
 
        #endregion
    }
 
对于Close来讲不属于真正意义上的释放,除了注意它须要显示被调用外,我在此对它很少说了。而对于析构函数而言,不是在对象离开做用域后马上被执行,只有在关闭进程或者调用GC.Collect方法的时候才被调用,参看以下的代码运行结果。
        private void Create()
        {
            DisposeClass myClass = new DisposeClass();
        }
 
        private void CallGC()
        {
            GC.Collect();
        }
 
        // Show destructor
        Create();
        Debug.WriteLine( "After created!" );
        CallGC();
 
运行的结果为:
After created!
Destructor called!
 
显然在出了 Create函数外, myClass对象的析构函数没有被马上调用,而是等显示调用GC.Collect才被调用。
 
对于Dispose来讲,也须要显示的调用,可是对于继承了IDisposable的类型对象可使用using这个关键字,这样对象的Dispose方法在出了using范围后会被自动调用。例如:
    using( DisposeClass myClass = new DisposeClass() )
    {
        //other operation here
    }
 
如上运行的结果以下:
Dispose called!
 
那么对于如上 DisposeClass类型的Dispose实现来讲,事实上GC还须要调用对象的析构函数, 按照前面的GC流程来讲,GC对于须要调用析构函数的对象来讲,至少通过两个步骤,即首先调用对象的析构函数,其次回收内存。也就是说,按照上面所写的Dispose函数,虽然说被执行了,可是GC仍是须要执行析构函数,那么一个完整的Dispose函数,应该经过调用GC.SuppressFinalize(this )来告诉GC,让它不用再调用对象的析构函数中。那么改写后的DisposeClass以下:
    ///<summary>
    /// The class to show three disposal function
    ///</summary>
    public class DisposeClass:IDisposable
    {
        public void Close()
        {
            Debug.WriteLine( "Close called!" );
        }
 
        ~DisposeClass()
        {
            Debug.WriteLine( "Destructor called!" );
        }
 
        #region IDisposable Members
 
        public void Dispose()
        {
            // TODO: Add DisposeClass.Dispose implementation
            Debug.WriteLine( "Dispose called!" );
            GC.SuppressFinalize( this );
        }
 
        #endregion
    }
 
经过以下的代码进行测试。
        private void Run()
        {
            using( DisposeClass myClass = new DisposeClass() )
            {
                //other operation here
            }
        }
 
        private void CallGC()
        {
            GC.Collect();
        }
 
        // Show destructor
        Run();
        Debug.WriteLine( "After Run!" );
        CallGC();
 
运行的结果以下:
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#程序所使用的内存是受托管的,但不意味着滥用,好地编程习惯有利于提升代码的质量以及程序的运行效率。