析构方法: ide
咱们知道引用类型都有构造方法(constructor),相对应的也有一个析构方法(destructor).顾名思义,构造方法,就是在建立这个对象时,要执行的方法。例如,咱们能够经过构造方法,this
初始化字段。析构方法,就是当这个对象被垃圾回收后(garbage collected,咱们称回收对象内存为垃圾回收 garbage collection),要执行的方法。关于析构方法,须要你们注意的是,垃圾回收一个对象,并非析构方法完成的(下面会讲到垃圾回收的工做原理),析构方法只有在对象被垃圾回收后才执行。也就是说,析构方法对于一个对象来说,不是必须的。不少时候,若是加上它,反而很差(下面讲garbage collector怎样工做时,就会明白很差的缘由)。
spa
既然垃圾回收不归析构方法负责,那么它有什么用呢?由于垃圾回收是CLR自动执行的,CLR只能处理受管理资源(managed resource),那些不受管理资源(unmanaged resource)就须要咱们线程
本身去处理了。例如文件读取(file stream),当对象结束时,咱们须要把文件流关掉。关掉文件流的代码,就要在析构方法中。也就是说,析构方法的用处,在处理不受管理资源时用处比较大。
看个例子:设计
class FileProcessor { FileStream file=null;
public FileProcess(string fileName) { this.file=file.OpenRead(fileName); //open file for reading } ~FileProcess() //析构方法与构造方法很类似,不一样的是析构方法要加一个~ { this.file.Close(); //close file } }
file对象,是CRL垃圾回收,当file被垃圾回收后,运行析构方法FileProcess,此时咱们将非管理资源关掉。this.file.Close().code
这里有几条对析构方法的限制:对象
1.只有引用类型才能够有析构方法。blog
struct MyStruct(){ ~MyStruct(){....}//结构是值类型,因此不能有析构方法 }
2.不能对析构方法提供访问修饰符.继承
public ~FileProcessor(){};//错误的
3.析构方法不能够有参数.接口
~FileProcessor(int param) { ....// //错误的 };
之因此会有这三条限制,是由于析构方法只能由CLR调用,本身不能够调用。由于,你不知道引用对象何时被垃圾回收了,只有对象被垃圾回收了,程序才会本身调用析构方法。
在内部,程序会将咱们写的析构方法,转换一下。例如:
class FileProcessor { ~fileProcessor(){...} //析构方法 } class FileProcessor { protected override void Finalize() { try{ } finally{ base.Finalize(); //CLR会将析构方法转变成这个 } } }
咱们把执行析构方法的过程,称为结束(finalization,或终结。)
垃圾回收机制(garbage collector)
咱们上边提到,回收内存空间,回收不用的引用类型对象的过程称为垃圾回收(garbage collection).这个过程是由CLR经过Garbage collector这样一个机制去运行的。
当咱们在程序中建立变量,会在内存中开辟一段空间。电脑的内存不是无限大的,咱们须要在变量超越定义的范围(程序再也不须要这个变量了)时,对它所占的内存进行管理,处理这些内存。当变量再也不被使用时,须要把内存回收。值类型变量回收内存,很是简单。
当它超出定义的范围时就会自动被毁掉,被占的内存也会自动回收。超出定义的范围,指的是当它再也不被使用,不能再被使用。引用类型变量回收内存,比较麻烦。例如:
fileProcessor myFp=new fileProcessor(); fileProcessor referenceToMyFp=myFp;
想一下这种状况,myFp对象已经超出定义的范围。此时咱们去回收内存,要把myFp引用堆上的内存回收。但是,偏偏此时,referenceToMyFp还在引用准备回收的内存,若是此时把内存回收,当程序运行referenceToMyFp时,程序就会出错。因此,只用当全部引用对象都超出定义的范围时,也就是都再也不使用时,才能够去回收这些对象引用的内存。确保程序中这些指向同一起的引用对象所有再也不使用,是很困难,很复杂的.因此C#设计者,把处理引用类型回收内存的工做,交给了CLR(Common language running).CLR利用garbage collector机制,来处理这些事情。
垃圾回收机制工做原理
garbage collector在本身的线程中工做,在特定的时间执行。通常,当程序运行到一个方法的最后时,就会工做。它工做时,其余线程就会暂时中止工做。由于,garbage collector可能会移除或者更新对象引用。
1.garbage collector会建立一张表,表里存放全部的可获得对象(reachable objects,.可获得对象,说白了就是指那些还在使用,不能回收内存的对象。)。
2.检查一下那些不可获得对象(unreachable objects,就是那些超出定义范围,须要回收内存的对象),看看他们是否有析构方法。(destructor),若是有,就把这些对象放入一个叫作
freachable queue的队列里。
3.把那些不可获得对象,且没有析构方法的对象所指向的内存地址回收。它是经过将那些可得对象在堆上的地址下移,这样堆上面就留出了可用的内存。此时,garbage collector也会更新堆
上的引用地址。(由于,地址有变化)。
4.此时,程序中其余的线程恢复工做。
5.garbage collector 经过调用本身的Finalize方法来结束不可获得对象,且有析构方法的对象。(前面咱们讲了,析构方法不是必须的,有时候会给程序带来复杂,累赘。若是,没有析构方法,当
CLR运行Garbage collector时,第五步就能够省去)。
资源管理
有些资源很稀缺,稀缺到不能等到CLR去调用析构方法去处理。例如database connections,file handles.此时,咱们就须要写一个dispose方法,手动去处理资源。(dispose能够换成任何名
字,这里只是举个例子)。例如:
TextReader reader=new StreamReader(filename); string line; while((line=reader.ReadLine())!=null) { Console.WriteLine(line); } reader.Close();
这里,Close就是一个dispose方法,手动去关掉文件流。可是,这样有个问题,当出现异常时,有可能致使reader.Close()不被执行,这样资源一直被占用。因此,咱们要改写成:
TextReader reader=new StreamReader(filename); try { string line; while((line=reader.ReaderLine())!=null) { Console.WriteLine(line); } } finally { reader.Close(); }
这样不管如何,reader.Close()都会被执行,资源都会被释放。不过即使改写成这样,也不是最完美的。由于,当咱们执行完finally块儿内的代码,也就是在try finally以后,咱们有可能
无心中使用reader对象,就是释放掉资源后的reader对象。此时,咱们能够用using 语句来解决这些不足。咱们将上面的代码改为using以后:
using(TextReader reader=new StreamReader(filename)) { string line; while((line=reader.ReadLine())!=null) { Console.Writle(line); } }
执行完using以后,资源自动释放,越过using范围,对象不能用。一个对象若是要被支持使用using,该对象必须实现IDispose接口。
咱们本身建立一个类,继承IDispose接口,让它能够用在USING语句中。这里咱们应该区分一下析构方法,与dispose方法。咱们知道析构方法必定会执行,可是不知道何时执行。咱们
知道dispose方法何时执行,可是不知道它会不会执行,由于类里有dispose方法,不表明必定会把这个类用在using语句中。此时,咱们就能够经过析构方法来调用dispose方法,这样可
以保证dispose方法必定被调用。