CLR的诡异~

ninputer在关于“值类型的Finalize不会被调用”中(http://blog.joycode.com/lijianzhong/archive/2005/01/13/42991.aspx#FeedBack)评论到“VB对Finalize管的可松呢,可以直接重写、直接调用、允许不调用父类的Finalize,或者多次调用父类的Finalize等等…… 完全不像C#”。

其实C#的Finalize方法看起来只是比VB的好一点,但仍然有非常隐蔽的问题。问题如下。

首先来看如下的代码:

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#编译器并没有禁止我们定义“普通”的Finalize,

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#编译器呈现的行为是诡异的,因为这种结果放到哪里都是难以自圆其说的。我曾经为此挖掘了sscli源代码很长时间,但是就是找不到原因。

这一方面是C#编译器的一个bug,另一方面也是CLR的一个bug。这个bug从.NET Framework的1.0版(VS.NET 2002),到1.1版(VS.NET 2003),以及Alpha版本的Longhorn操作系统中自带的1.2版都存在。后来我写信给C#的产品经理Eric Gunnerson(http://blogs.msdn.com/ericgu/)告诉他们这个bug。Eric Gunnerson随后回信告诉我他们会修复这个bug。

我现在使用Visual C# Express 2005编译器编译(version 8.00.41013)上述代码,后面两种修改版都会得到一个warning:

warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?

但是如果不理会这样的警告,得到的exe文件执行行为仍然是非常奇怪。也就是说CLR中的bug仍然没有fix。我个人认为对于C#编译器来说,warning是不够的,应该彻底禁止定义这样的Finalize方法。

 

实际上在我的Effective .NET (in C#)一书的draft里也有这样一个条款:

# 不要在一个类中有定义任何Finalize方法的念头,因为那样会对你的“析构器链”造成潜在的严重的伤害。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值