【译序:C#入门文章。请注意:所有程序调试环境为Microsoft Visual Studio.NET 7.0 Beta2和 Microsoft .NET Framework SDK Beta2。限于译者时间和能力,文中倘有讹误,当以英文原版为准】
在最近发表于《MSDN Magazine》(2001年7月刊)上的一篇文章里,我讲了“从C++转移到C#,你应该了解些什么?”。在那篇文章里,我说过C#和C++的语法很相似,转移过程中的困难并非来自语言自身,而是对受管制的.NET环境的适应和对庞大的.NET框架的理解。
我已经编辑了一个C++和C#语法不同点的列表(可在我的WEB站点上找到这个列表。在站点上,点击Books可以浏览《Programming C#》,也可以点击FAQ看看)。正如你所意料的,很多语法上的改变是小而琐细的。有一些改变对于粗心的C++程序员来说甚至是隐蔽的陷阱,本文将集中阐述十个危险的陷阱。
陷阱一.非确定性终结和C#析构器
理所当然,对于大多数C++程序员来说,C#中最大的不同是垃圾收集。这就意味你不必再担心内存泄漏以及确保删除指针对象的问题。当然,你也就失去了精确控制销毁对象时机的能力。实际上,C#中并没有显式的析构器。
如果你在处理一个未受管制的资源,当你用完时,你需要显式地释放那些资源。可通过提供一个Finalize方法(称为终结器)隐式控制资源,当对象被销毁时,它将被垃圾收集器调用。
终结器只应该释放对象携带的未受管制的资源,而且也不应该引用别的对象。注意:如果你只有一些受管制的对象引用那你用不着也不应该实现Finalize方法—它仅在需处理未受管制的资源时使用。因为使用终结器要付出代价,所以,你只应该在需要的方法上实现(也就是说,在使用代价昂贵的、未受管制的资源的方法上实现)。
永远不要直接调用Finalize方法(除了在你自己类的Finalize里调用基类的Finalize方法外【译注:此处说法似乎有误,参见下面译注!】),垃圾收集器会帮你调用它。
C#的析构器在句法上酷似C++的析构器,但它们本质不同。C#析构器仅仅是声明Finalize方法并链锁到其基类的一个捷径【译注:这句话的意思是,当一个对象被销毁时,从最派生层次的最底层到最顶层,析构器将依次被调用,请参见后面给出的完整例子】。因此,以下写法:
~MyClass()
{
//do work here
}
和如下写法具有同样效果:
MyClass.Finalize()
{
// do work here
base.Finalize();//
}
【译注:上面这段代码显然是错误的,首先应该写为:
class MyClass
{
void Finalize()
{
// do work here
base.Finalize();//这样也不可以!编译器会告诉你不能直接调用基类的Finalize方法,它将从析构函数中自动调用。关于原因,请参见本小节后面的例子和陷阱二的有关译注!
}
}
下面给出一个完整的例子:
using System;
class RyTestParCls
{
~RyTestParCls()
{
Console.WriteLine("RyTestParCls's Destructor");
}
}
class RyTestChldCls: RyTestParCls
{
~RyTestChldCls()
{
Console.WriteLine("RyTestChldCls's Destructor");
}
}
public class RyTestDstrcApp
{
public static void Main()
{
RyTestChldCls rtcc = new RyTestChldCls();
rtcc = null;
GC.Collect();//强制垃圾收集
GC.WaitForPendingFinalizers();//挂起当前线程,直至处理终结器队列的线程清空该队列
Console.WriteLine("GC Completed!");
}
}
以上程序输出结果为:
RyTestChldCls's Destructor
RyTestParCls's Destructor
GC Completed!
注意:在CLR中,是通过重载System.Object的虚方法Finalize()来实现虚方法的,在C#中,不允许重载该方法或直接调用它,如下写法是错误的:
class RyTestFinalClass
{
override protected void Finalize() {}//错误!不可重载System.Object方法。
}
同样,如下写法也是错误的:
class RyTestFinalClass
{
public void SelfFinalize() //注意!这个名字是自己取的,不是Finalize
{
this.Finalize()//错误!不能直接调用Finalize()
base.Finalize()//错误!不能直接调用基类Finalize()
}
}
class RyTestFinalClass
{
protected void Finalize() //注意!这个名字和上面不一样,同时,它也不是override的,这是可以的,这样,你就隐藏了基类的Finalize。
{
this.Finalize()//自己调自己,当然可以,但这是个递归调用你想要的吗?J
base.Finalize()//错误!不能直接调用基类Finalize()
}
}
对这个主题的完整理解请参照陷阱二。】
陷阱二.Finalize和Dispose
显式调用终结器是非法的,Finalize方法应该由垃圾收集器调用。如果是处理有限的、未受管制的资源(比如文件句柄),你或许想尽可能快地关闭和释放它,那你应该实现IDisposable接口。这个接口有一个Dispose方法,由它执行清除动作。类的客户负责显式调用该Dispose方法。Dispose方法允许类的客户说“不要等Finalize了,现在就干吧!”。
如果提供了Dispose方法,你应该禁止垃圾收集器调用对象的Finalize方法—既然要显式进行清除了。为了做到这一点,应该调用静态方法GC.SuppressFinalize,并传入对象的this指针,你的Finalize方法就能够调用Dispose方法。
你可能会这么写:
public void Dispose()
{
// 执行清除动作
// 告诉垃圾收集器不要调用Finalize
GC.SuppressFinalize(this);
}
public override void Finalize()
{
Dispose();
base.Finalize();
}
【译注:以上这段代码是有问题的,请参照我在陷阱一中给的例子。微软站点上有一篇很不错的文章(Gozer the Destructor),说法和这儿基本一致,但其代码示例在Microsoft Visual Studio.NET 7.0 Beta2和 Microsoft .NET Framework SDK Beta2都过不了,由于手头没有Beta1比对,所以,现在还不能确定是文章的笔误,还是因为Beta1和Beta2的不同而导致,还是我没有准确地理解这个问题。比如下