zoukankan      html  css  js  c++  java
  • 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栏目)。

  • 相关阅读:
    poj 3280 Cheapest Palindrome(区间DP)
    POJ 2392 Space Elevator(多重背包)
    HDU 1285 定比赛名次(拓扑排序)
    HDU 2680 Choose the best route(最短路)
    hdu 2899 Strange fuction (三分)
    HDU 4540 威威猫系列故事――打地鼠(DP)
    HDU 3485 Count 101(递推)
    POJ 1315 Don't Get Rooked(dfs)
    脱离eclipse,手动写一个servlet
    解析xml,几种方式
  • 原文地址:https://www.cnblogs.com/huqingyu/p/20477.html
Copyright © 2011-2022 走看看