C#中的对象资源释放

c# 自身对于所有托管对象(managed object)都是可以不用程序员去干预的(注:可以不用干预,当然资源耗费很多且必要时也可以去做一些干预来提升程序性能,比如通过代码部分控制垃圾回收),但对于非托管资源(文件、流、数据库连接、GDI+对象、COM对象等等)都需要程序来控制资源的释放。

释放资源主要有两种方式,其一是对象实现IDisposable接口,由程序员调用IDisposable.Dispose()方法来释放资源;其二是通过重写继承自Object对象的虚方法Finalize()来释放资源。

(一)IDisposable         ----------(注:实现IDisposable 成为 "可处置类型")

      public interface IDisposable

       {void Dispose();}

     说明:垃圾回收器GC不支持IDisposable接口,不能通过自动的垃圾回收机制或手工调用GC.Collect()方法来调用Disposable方法。

    重要点:只有通过程序显式或非显式调用Dispose方法,才会通过该方法释放资源。所谓显示调用,就是直接在代码中写Dispose()来带调用。关于隐式调用,常见的有两种:

(1)其一就是使用using关键字

     using(StreamWriter sw= new StreamWriter(NewFilePath,true))

     {

           //处理完代码后,会调用Dispose方法。即使发生了异常,也会调用Dispose方法。本质上中间代码是转换为try{}finally{}的,而Dispose方法是在finally{}中调用的,所以不担心因为异常而没有调用到Dispose方法 

    }

(2)关于Dispose方法的隐式调用,另一种常见的是对Dispose的间接调用而已,常见的是一些类库中的别名调用。比如

       FileSream fs=new FileStream("abc.txt“,FileMode.OpenOrCreate);

      //dosomething

      fs.close();    //close()释放资源,实质上就是隐式调用了Dispose()释放了资源。注:如果不调用close()而调用Dispose(),其效果是一样的。

(二)Finalize()方法与析构函数~MyClass()  -----(注:重载Finalize称作”可终结类型“)

         要点:Finalize是由Object基类定义的虚方法;垃圾回收的时候GC会调用Finalize()方法;在自己的程序中,通过c#是不能直接重载Finalize()方法的,会出现编译错误,what?why?how?那怎么办呢?答案是:用析构函数~MyClass()来代替Finalize()方法,在析构函数中写释放资源的代码。

class MyClass

{

    protected override void Finalize(){}    //是不能编译的,编译不通过

//为什么会这样呢。Finalize方法本来就是Object类定义的受保护的虚方法(protected virtual),这里却不能重写???

//不知道微软是怎么处理的,但就是不能继承,咱也没有办法的,那就不要这么做了。微软要我们直接写析构函数~MyClass(){}

}

class MyClass

{

    protected override void Finalize(){}    //是不能编译的,编译不通过

   ~MyClass()

      {

           MyReleaseSourceCode;         //在这里写释放资源的代码

     }

}

经过仔细查证,发现:析构函数经过编译后,在中间代码中就会转换为Finalize()方法,所以c#的析构函数本质上就是Finalize()方法。但是,通过析构函数转换为Finalize()方法的过程中,编译器会添加一些额外的代码。通过ildasm.exe查看c#析构函数编译过的代码,可以发现编译器加入了一些错误检测代码。分析中间码可以发现,程序员自己写的代码都会放在中间代码的try{}块中,另外在中间代码的finally块中放置了一些代码,保证基类的Finalize方法总是被执行。以下是编译后的中间码部分展示,用来说明编译后的析构函数的样子。

.method family hidebysig virtual instance void Finalize() cil managed            //很明显析构函数编译后就是Finalize()方法

{

    .maxstack 1

    .try

    {

        ....

        Compiled  MyReleaseSourceCode    //这个地方方式编译后的释放资源的代码

        ...

    }

    finally

    {

        ......

        IL_0014:

            call instance void [mscorlib] System.Object::Finalize()                 //调用父类的Finalize方法

        ......

    }

}

因此,析构函数(或者说Finalize方法)是先调用本类需要析构的资源,然后调用父类的Finalize()继续析构的,能够保证基类的资源被正确释放。

比如,以下代码:

    class Animal
    {
        ~Animal()
        {
            Console.WriteLine("Animal Destructed");
        }
    }
    class Person:Animal
    {
        ~Person()
        {
            Console.WriteLine("Person Destructed");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person();
            p = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.Read();
        }
    }

        上述代码执行后,会输出:

          Person Destructed
           Animal Destructed

      也就是先执行子类的析构函数,后执行父类的析构函数。

     (三)同时实现IDisposable并重载Finalize()方法的类(--------注:成为可处置、可终结的类型)

               如果仅仅实现IDisposable方法而没有重载Finalize()(说明:重载Finalize就是定义了析构函数,因为析构函数编译后就是重载的Finalize()方法),那么如果忘了调用Dispose方法,那么非托管资源可能会永远留在内存中。

              可以既实现IDisposable方法也重载Finalize()方法。如果在Dispose方法中调用GC.SuppressFinalize()通知垃圾回收器跳过终结过程,则在Dispose()后,当GC在回收的时候,就不会调用Finalize()方法中的代码。但如果用户忘记了调用 Dispose方法,那么在垃圾回收的时候就会调用释放资源的代码。

            微软提供的样板代码如下:

    class MyClass:IDisposable
    {
        private bool disposed = false;

        public void Dispose()
        {
            DisposeImpl(true);
            GC.SuppressFinalize(this);

        }

        private void DisposeImpl(bool disposing)
        {
            if(!this.disposed)
            {
                if(disposing)
                {
                    //这里释放托管的资源
                }

                //这里释放非托管的资源
            }
            disposed = true;
        }

        ~MyClass()
        {
            DisposeImpl(false);
        }
    }

 

 

你可能感兴趣的:(C#语言)