C#中的析构函数
析构函数writeline方法的作用
析构函数(destructor) 与构造函数相反,当对象脱离其作⽤域时(例如对象所在的函数已调⽤完毕),系统⾃动执⾏析构函数。析构函数往往⽤来做“清理善后” 的⼯作(例如在建⽴对象时⽤new开辟了⼀⽚内存空间,应在退出前在析构函数中⽤delete释放)。
以C++语⾔为例,析构函数名也应与类名相同,只是在函数名前⾯加⼀个波浪符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有⼀个析构函数,不能重载。如果⽤户没有编写析构函数,编译系统会⾃动⽣成⼀个缺省的析构函数,它也不进⾏任何操作。所以许多简单的类中没有⽤显式的析构函数。
解构器
我们知道,‘解构器’被⽤来清除类的事例。当我们在C#中使⽤解构器是,我们必须记住以下⼏点:
⼀个类只能有⼀个解构器。
解构器不能被继承或重载。
解构器不能被调⽤。他们是⾃动被(编译器)调⽤的。
解构器不能带修饰或参数。
下⾯是类MyClass解构器的⼀个声明:
~ Class()
{
// Cleaning up code goes here
}
程序员不能控制解构器何时将被执⾏因为这是由垃圾收集器决定的。垃圾收集器检查不在被应⽤程序使⽤的对象。它认为这些条件是符合清楚的并且收回它们的内存。解构器也在程序退出时被调⽤。当解构器执⾏时其背后所发⽣的那⼀幕是解构器隐式调⽤对象基类的Object.Finalize⽅法。因此上述解构器代码被隐含转化成:
protected override void Finalize()
{
try
{
// Cleaning up .
}
finally
{
base.Finalize();
}
}
现在,让我们看⼀个解构器怎样被调⽤的例⼦。我们有三个类A,B和C 。B派⽣⾃A,C派⽣⾃B。每个类有它们⾃⼰的构造器和解构。在类App的main函数中,我们创建C的对象。
using System;
class A
{
public A()
{
Console.WriteLine("Creating A");
}
~A()
{
Console.WriteLine("Destroying A");
}
}
class B:A
{
public B()
{
Console.WriteLine("Creating B");
}
~B()
{
Console.WriteLine("Destroying B");
}
}
class C:B
{
public C()
{
Console.WriteLine("Creating C");
}
~C()
{
Console.WriteLine("Destroying C");
}
}
class App
{
public static void Main()
{
C c=new C();
Console.WriteLine("Object Created ");
Console.WriteLine("Press enter to Destroy it");
Console.ReadLine();
c=null;
//GC.Collect();
Console.Read();
}
}
正如我们预料的,基类的构造器将会被执⾏并且程序会等待⽤户按‘enter’。当这个发⽣,我们把类C的对象置为null.但解构器没有被执⾏..!!??正像我们所说的,程序员⽆法控制解构器何时被执⾏因为这是由垃圾搜集器决定的。但程序退出时解构器被调⽤了。你能通过重定向程序的o/p到⽂本⽂件来检查这个。我将它输出在这⾥。注意到基类的解构器被调⽤了,因为在背后base.Finalize()被调⽤了。
Creating A
Creating B
Creating C
Object Created
Press enter to Destroy it
Destroying C
Destroying B
Destroying A
所以,如果⼀旦你使⽤完对象你就想调⽤解构器,你该怎么做?有两个⽅法:
调⽤垃圾搜集器来清理。
实现IDisposable的Dispose⽅法。
调⽤垃圾搜集器
你能通过调⽤GC.Collect⽅法强制垃圾搜集器来清理内存,但在⼤多数情况下,这应该避免因为它会导致性能问题。在上⾯的程序中,在GC.Collect()处移除注释。编译并运⾏它。现在,你能看到解构器在控制台中被执⾏了。
实现IDisposable接⼝
IDisposable 接⼝包括仅有的⼀个公共⽅法,其声明为void Dispose()。我们能实现这个⽅法来关闭或
释放⾮托管资源如实现了这个接⼝的类事例所控制的⽂件,流,和句柄等。这个⽅法被⽤做所有任务联合对象的资源释放。当实现了这个⽅法,对象必须寻求确保所有拥有的资源被继承结构中关联的资源也释放(不能把握,翻不出来)。
class MyClass:IDisposable
{
public void Dispose()
{
//implementation
}
}
当我们实现了IDisposable接⼝时,我们需要规则来确保Dispose被适当地调⽤。
联合使⽤解构器和IDisposable接⼝
Public class MyClass:IDisposable
{
private bool IsDisposed=false;
public void Dispose()
{
Dispose(true);
GC.SupressFinalize(this);
}
protected void Dispose(bool Diposing)
{
if(!IsDisposed)
{
if(Disposing)
{
//Clean Up managed resources
}
//Clean up unmanaged resources
}
IsDisposed=true;
}
~MyClass()
{
Dispose(false);
}
}
在这⾥重载了Dispose(bool)来做清理⼯作,并且所有的清理代码都仅写在这个⽅法中。这个⽅法被解构器和IDisposable.Dispose()两着调⽤。我们应该注意Dispose(bool)没有在任何地⽅被调⽤除了在IDisposable.Dispose()和解构器中。
当⼀个客户调⽤IDisposable.Dispose()时,客户特意地想要清理托管的和⾮托管资源,并且因此完成清理⼯作。有⼀件你必须注意的事情是我们在清理资源之后⽴即调⽤了GC.SupressFinalize(this)。这个⽅法通知垃圾搜集器不需要调⽤解构器,因为我们已经做了清理。
注意上⾯的例⼦,解构器使⽤参数false调⽤Dispose。这⾥,我们确信垃圾搜集器搜集了托管资源。我们仅仅做⾮托管资源的清理。
结论
尽管如此我们花费⼀些时间实现IDisposable接⼝,如果客户不能合适地调⽤它们会怎样?为此C#有⼀个酷的解决⽅案。‘using’代码块。它看起来像这样:
using (MyClass objCls =new MyClass())
{
}
当控制从using块通过成功运⾏到结束或者抛出异常退出时,MyClass的IDispose.Dispose()将会被执⾏。记住你例⽰的对象必须实现System.IDisposable接⼝。using语句定义了哪个对象将被清除的⼀个范围。
注:
构造函数与析构函数的区别:
构造函数和析构函数是在类体中说明的两种特殊的成员函数。
构造函数的功能是在创建对象时,使⽤给定的值来将对象初始化。
析构函数的功能是⽤来释放⼀个对象的。在对象删除前,⽤它来做⼀些清理⼯作,它与构造函数的功能正好相反。