C#中的每个类型都表明一种资源,而资源又分为两类:html
若是咱们的类型使用到了非托管资源,或者须要显式地释放托管资源,那么就须要让类型继承接口IDisposable,这毫无例外。这至关于告诉调用者:类型对象是须要显式释放资源的,你须要调用类型的Dispose方法。,一个标准的继承了IDisposable接口的类型应该像下面这样去实现。这种实现咱们称为Dispose模式:程序员
public class SampleClass:IDisposable { //演示建立一个非托管资源 private IntPtr nativeResource=Marshal.AllocHGlobal(100); //演示建立一个托管资源 private AnotherResource managedResource=new AnotherResource(); private bool disposed=false; ///<summary> ///实现IDisposable中的Dispose方法 ///</summary> public void Dispose() { //必须为true Dispose(true); //通知垃圾回收机制再也不调用终结器(析构器) GC.SuppressFinalize(this); } ///<summary> ///不是必要的,提供一个Close方法仅仅是为了更符合其余语言(如C++)的规范 ///</summary> public void Close() { Dispose(); } ///<summary> ///必需的,防止程序员忘记了显式调用Dispose方法 ///</summary> ~SampleClass() { //必须为false Dispose(false); } ///<summary> ///非密封类修饰用protected virtual ///密封类修饰用private ///</summary> ///<param name="disposing"></param> protected virtual void Dispose(bool disposing) { if(disposed) { return; } if(disposing) { //清理托管资源 if(managedResource!=null) { managedResource.Dispose(); managedResource=null; } } //清理非托管资源 if(nativeResource!=IntPtr.Zero) { Marshal.FreeHGlobal(nativeResource); nativeResource=IntPtr.Zero; } //让类型知道本身已经被释放 disposed=true; } public void SamplePublicMethod() { if(disposed) { throw new ObjectDisposedException("SampleClass","SampleClass is disposed"); } //省略 } }
若是类型须要显式释放资源,那么必定要继承IDispose接口。
承IDispose接口也为实现语法糖using带来了便利。在C#编码中,若是像下面这样使用using,编译器会自动为咱们生成调用Dispose方法的IL代码:数据库
using(SampleClass c1=new SampleClass()) { //省略 }
至关于网络
SampleClass c1; try{ c1=new SampleClass(); //省略 } finally { c1.Dispose(); }
在标准的Dispose模式中,咱们注意到一个以~开头的方法,以下所示:this
///<summary> ///必须,防止程序员忘记了显式调用Dispose方法 ///</summary> ~SampleClass() { //必须为false Dispose(false); }
这个方法叫作类型的终结器。提供终结器的意义在于:咱们不能奢望类型的调用者确定会主动调用Dispose方法,基于终结器会被垃圾回收器调用这个特色,它被用做资源释放的补救措。编码
对于没有继承IDisposable接口的类型对象,垃圾回收器则会直接释放对象所占用的内存;而对于实现了Dispose模式的类型,在每次建立对象的时候,CLR都会将该对象的一个指针放到终结列表中,垃圾回收器在回收该对象的内存前,会首先将终结列表中的指针放到一个freachable队列中。同时,CLR还会分配专门的线程读取freachable队列,并调用对象的终结器,只有到这个时候,对象才会真正被标识为垃圾,而且在下一次进行垃圾回收时释放对象占用的内存。线程
能够看到,实现了Dispose模式的类型对象,起码要通过两次垃圾回收才能真正地被回收掉,由于垃圾回收机制会首先安排CLR调用终结器。基于这个特色,若是咱们的类型提供了显式释放的方法来减小一次垃圾回收,同时也能够在终结器中提供隐式清理,以免调用者忘记调用该方法而带来的资源泄漏。设计
注意1 在有的文档中,终结器也称作析构器。指针
注意2 若是调用者已经调用Dispose方法进行了显式地资源释放,那么,隐式释放资源(也就是终结器)就没有必要再运行了。
FCL中的类型GC提供了静态方法SuppressFinalize来通知垃圾回收器这一点。注意查看Dispose方法:code
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
一个类型的Dispose方法应该容许被屡次调用而不抛异常。鉴于这个缘由,类型内部维护了一个私有的布尔型变量disposed,以下所示:
private bool disposed=false;
在实际清理代码的方法中,加入了以下的判断语句:
if(disposed) { return; }
在//省略部分的代码,方法的最后为disposed赋值为true:disposed=true;这意味着若是类型已经被清理过一次,那么清理工做将再也不进行。对象被调用过Dispose方法,并不表示该对象已经被置为null,且被垃圾回收机制回收过内存,已经完全不存在了。事实上,对象的引用可能还在。可是,对象被Dispose过,说明对象的正常状态已经不存在了,此时若是调用对象公开的方法,应该会为调用者抛出一个ObjectDisposedException。
真正实现IDisposable接口的Dispose方法并无作实际的清理工做,它实际上是调用了下面这个带布尔参数且受保护的虚方法:
///<summary> ///非密封类修饰用protected virtual ///密封类修饰用private///</summary> ///<param name="disposing"></param> protected virtual void Dispose(bool disposing) { //省略代码 }
之因此提供这样一个受保护的虚方法,是由于考虑了这个类型会被其余类继承的状况。若是类型存在一个子类,子类也许会实现本身的Dispose模式。受保护的虚方法用来提醒子类:必须在实现本身的清理方法时注意到父类的清理工做,即子类须要在本身的释放方法中调用base.Dispose方法。
若是不为类型提供这个受保护的虚方法,颇有可能让开发者设计子类的时候忽略掉父类的清理工做。因此,基于继承体系的缘由,要为类型的Dispose模式提供一个受保护的虚方法。
Dispose模式设计的思路基于:若是调用者显式调用了Dispose方法,那么类型就该循序渐进地将本身的资源所有释放。若是调用者忘记调用Dispose方法了,那么类型就假定本身的全部托管资源会所有交给垃圾回收器回收,因此不进行手工清理。理解了这一点,咱们就理解了为何在Dispose方法中,虚方法传入的参数是true,而在终结器中,虚方法传入的参数是false。
咱们将C#中的类型分为:普通类型和继承了IDisposable接口的非普通类型。非普通类型除了那些包含托管资源的类型外,还包括类型自己也包含一个非普通类型的字段的类型。
在标准的Dispose模式中,咱们对非普通类型举了一个例子:一个非普通类型AnotherResource。因为AnotherResource是一个非普通类型,因此若是如今有这么一个类型,它组合了AnotherResource,那么它就应该继承IDisposable接口,代码以下所示:
class AnotherSampleClass:IDisposable { private AnotherResource managedResource=new AnotherResource(); private bool disposed=false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
类型AnotherSampleClass虽然没有包含任何显式的非托管资源,可是因为它自己包含了一个非普通类型,因此咱们仍旧必须为它实现一个标准的Dispose模式。
除此之外,类型拥有本机资源(即非托管类型资源),它也应该继承IDisposable接口。
不少人会注意到:垃圾回收机制自动为咱们隐式地回收了资源(垃圾回收器会自动调用终结器),因而不由会问:为何还要主动释放资源呢?咱们来看如下这个例子:
private void buttonOpen_Click(object sender,EventArgs e) { FileStream fileStream=new FileStream(@"c:\test.txt",FileMode.Open); } private void buttonGC_Click(object sender,EventArgs e) { System.GC.Collect(); }
若是连续两次单击打开文件按钮,系统就会报错,以下所示:
IOException:文件"c:\test.txt" 正由另外一进程使用,所以该进程没法访问此文件。
如今来分析:在打开文件的方法中,方法执行完毕后,因为局部变量fileStream在程序中已经没有任何地方引用了,因此它会在下一次垃圾回收时被运行时标记为垃圾。那么,何时会进行下一次垃圾回收呢,或者说垃圾回收器何时才开始真正进行回收工做呢?微软官方的解释是,当知足如下条件之一时将发生垃圾回收:
但在本实例中,为了体会一下不及时回收资源的危害,因此进行了一次GC.Collect方法的调用,你们能够仔细体会运行这个方法所带来的不一样。
垃圾回收机制中还有一个“代”的概念。一共分为3代:0代、1代、2代。第0代包含一些短时间生存的对象,如示例代码中的局部变量fileStream就是一个短时间生存对象。当buttonOpen_Click退出时,fileStream就被丢到了第0代,但此刻并不进行垃圾回收,当第0代满了的时候,运行时会认为如今低内存的条件已知足,那时才会进行垃圾回收。因此,咱们永远不知道fileStream这个对象(或者说资源)何时才会被回收。在回收以前,它实际已经没有用处,却始终占据着内存(或者说资源)不放,这对应用系统来讲是一种极大的浪费,而且,这种浪费还会干扰程序的正常运行(如在本实例中,因为它始终占着文件资源,致使咱们不能再次使用这个文件资源了)。
不及时释放资源还带来另一个问题。在上面中咱们已经了解到,若是类型自己继承了IDisposable接口,垃圾回收机制虽然会自动帮咱们释放资源,可是这个过程却延长了,由于它不是在一次回收中完成全部的清理工做。本实例中的代码由于fileStream继承了IDisposable接口,故第一次进行垃圾回收的时候,垃圾回收器会调用fileStream的终结器,而后等待下一次的垃圾回收,这时fileStream对象才有可能被真正的回收掉。
了解了不及时释放资源的危害后,如今来改进这个程序,以下所示:
private void buttonOpen_Click(object sender,EventArgs e) { FileStream fileStream=new FileStream(@"c:\test.txt",FileMode.Open); fileStream.Dispose(); }
这确实是一种改进,可是咱们没考虑到方法中的第一行代码可能会抛出异常。若是它抛出异常,那么fileStream.Dispose()将永远不会执行。因而,再一次改进,以下所示:
FileStream fileStream=null; try { fileStream=new FileStream(@"c:\test.txt",FileMode.Open); } finally { fileStream.Dispose(); }
为了更进一步简化语句,还可使用语法糖“using”关键字。
在CLR托管的应用程序中,存在一个“根”的概念,类型的静态字段、方法参数,以及局部变量均可以做为“根”存在(值类型不能做为“根”,只有引用类型的指针才能做为“根”)。
当检查到方法内的“根”时,若是发现没有任何一个地方引用了局部变量,则无论是否已经显式将其赋值为null,都意味着该“根”已经被中止。而后,垃圾回收器会发现该根的引用为空,同时标记该根可被释放。
须要注意一下几点
在实际工做中,一旦咱们感受到本身的静态引用类型参数占用的内存空间比较大,而且用完后不会再使用,即可以马上将其赋值为null。这也许并没必要要,但这绝对是一个好习惯。试想在一个系统中那些时不时在类型中出现的静态变量吧!它们就那样静静地待在内存里,一旦被建立,就永远不会离开。或许咱们能够专门为此写一个小建议,那就是:尽可能少用静态变量。
序列化是指这样一种技术:把对象转变成流。相反的过程,咱们称为反序列化。在不少的场合都须要用到这项技术,例如:
有如下几方面的缘由,决定了要为无用字段标注不可序列化:
[Serializable] class Person { [NonSerialized] private decimal salary; public decimal Salary { get { return salary; } set { salary=value; } } private string name; public int Age{get;set;} public string Name { get { return name; } set { name=value; } [field:NonSerialized] public event EventHandler NameChanged; }
注意
1.因为属性本质上是方法,因此不能将NonSerialized特性应用于属性上,在标识某个属性不能被序列化时,自动实现的属性显然已经不能使用。
2.要让事件不能被序列化,需使用改进的特性语法field:NonSerialized。
特性(attribute)能够声明式地为代码中的目标元素添加注解。运行时能够经过查询这些托管模块中的元数据信息,达到改变目标元素运行时行为的目的。在System.Runtime.Serialization命名空间下,有4个这样的特性,下面是MSDN上对它们的解释:
示例:
[Serializable] class Person { public string FirstName; public string LastName; [NonSerialized] public string ChineseName; [OnDeserializedAttribute] void OnSerialized(StreamingContext context) { ChineseName=string.Format("{0}{1}",LastName,FirstName); } }
除了利用特性Serializable以外,咱们还能够注意到在序列化的应用中,经常会出现一个接口ISerializable。接口ISerializable的意义在于,若是特性Serializable,以及与其相配套的OnDeserializedAttribute、OnDeserializingAttribute、OnSerializedAttribute、OnSerializingAttribute、NonSerialized等特性不能彻底知足自定义序列化的要求,那就须要继承ISerializable了。
例如咱们要将一个对象反序列化成为另一个对象,就要都实现ISerializable接口,原理其实很简单,那就是在一个对象的GetObjectData方法中处理序列化,在另外一个对象的受保护构造方法中反序列化。
咱们将要实现的继承自ISerializable的类型Employee有一个父类Person,假设Person没有实现序列化,而如今子类Employee却要求可以知足序列化的场景。不过很遗憾,序列化器没有默认去处理Person类型对象,须要咱们在子类中受保护的构造方法和GetObjectData方法,为它们加入父类字段的处理
若有须要, 上一篇的《C#规范整理·泛型委托事件》也能够看看!