C#析构器的一个Bug

【2003/12/03】

http://www.lijianzhong.com/Index.asp
这是很早发现的C#语言的一个“bug”,总以为MS很快就会修复,可是从1.0等到1.1,再从1.1等到1.2(前面两个是.NET框架的Realse版,后面一个是Alpha版本的Longhorn操作系统中自带的.NET框架版本;1.2版的.NET框架中包含的是支持C# 2.0 Spec的编译器),一直没有改进。难道这就是C#设计者们对C#本来的设计?我说服不了自己。如果各位朋友有什么见解,希望能够就这个问题做深入的交流。问题如下:

using System;

public class Grandpapa
{
     ~Grandpapa(){ Console.WriteLine("Grandpapa.~Grandpapa");}
}

public class Parent:Grandpapa
{
     ~Parent(){ Console.WriteLine("Parent.~Parent");}
}

public class Son:Parent
{
     ~Son(){ Console.WriteLine("Son.~Son");}
}

public class App
{
     public static void Main()
     {
         Son s=new Son();
 
         GC.Collect();
         GC.WaitForPendingFinalizers();
     }
}

这段代码的运行结果毫无疑问是:

Son.~Son
Parent.~Parent
Grandpapa.~Grandpapa

这没什么问题。但是如果将Parent类重新定义如下,会出现什么情况呢?

public class Parent:Grandpapa
{
     protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
}

是的,运行结果变成了:

Son.~Son
Parent.Finalize

情况已经有些不妙了,我在Parent中定义了一个“普通”的Finalize方法,竟然被它的子类Son的析构器给调用了,是不是挺吓人?当然Finalize方法在C#中并不是一个“普通”的方法,析构器编译后就是一个有上述签名的Finalize方法。但C#编译器并没有对此给予警告或者禁止,C#规范也没有指出定义这样的Finalize方法就是在定义一个析构器——实际上也不是,只是上述代码的表现如此——甚至还有这样一句诱人犯错的话:The compiler behaves as if this method(Finalize), and overrides of it, do not exist at all。分析IL代码可以看出,Parent中定义的“普通”的Finalize方法实际上“欺骗”了它的子类。它的子类只关心其父类是否定义了Finalize(当然签名要为上述形式)方法,它并不关心那个Finalize方法是否具有“析构器”语义。

如果上述代码的行为通过理性分析还算可以接受的话,那么下面代码的运行结果就令人眩晕了,将Parent类重新定义如下(在上面的基础上添加了一个virtual关键字):

public class Parent:Grandpapa
{
     protected virtual void Finalize(){ Console.WriteLine("Parent.Finalize");}
}

编译后运行结果如下:

Grandpapa.~Grandpapa

这一次从IL代码的角度也解释不清了,我怀疑CLR对于析构器的判断是否还有另外的附加条件(也许是一个特殊的元数据标记),但无论如何C#编译器呈现的行为是诡异的,因为这种结果放到哪里都是难以自圆其说的。

说了这么多,并不是想说明C#有多么严重的缺陷,实际上在我看来C#的设计堪称完美。这只能算一个小小的“瑕疵”。最重要的问题是对于应用程序开发人员,必须了解C#语言的这个“瑕疵”,而不要被C#规范中模棱两可的语言和C#编译器的纵容而蒙蔽,否则稍有不慎就会出现非常严重且隐蔽的bug,对于服务器程序设计尤其如此。这是.NET框架带来的吗?不是,VB.NET就没有这样的问题。这是C#设计者们选择析构器语法来执行终止化操作所带来的弊病。To summarize:

我对C#设计者的建议是

要么彻底放弃析构器的语法(~ClassName)而直接象VB.NET中那样采用“特殊的Finalize方法”这样的概念;要么使编译器彻底禁止在一个类中定义任何的Finalize方法(后者在目前来说可能更reasonable一些)。

而对于C#开发人员的建议则是

不要在一个类中有定义任何Finalize方法的念头,因为那样会对你的类层次造成潜在的严重的伤害。顺便说一句,这一条建议已经收录进我规划中的一本书《C#锐利辞典》(详情见Books栏目)。

原文地址:https://www.cnblogs.com/huqingyu/p/20477.html