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何时会去释放和回收它所占用的内存。那么对于从
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回收。明白了这两种方法的意思后,你们在往本身的类中添加的接口时候,不要歪曲了这二者意思。
接下来讲说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使你们的印象更深。
首先是这三种方法的实现,大体以下:
///<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#程序所使用的内存是受托管的,但不意味着滥用,好地编程习惯有利于提升代码的质量以及程序的运行效率。