zoukankan      html  css  js  c++  java
  • C#中Finalize方法的问题

    C#中Finalize方法的问题

    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方法的念头,因为那样会对你的“析构器链”造成潜在的严重的伤害。

    发表于 2005年1月13日 19:55

    评论

    # re: C#中Finalize方法的问题 2005-1-13 21:06 寒星

    受救了。

    # re: C#中Finalize方法的问题 2005-1-13 21:06 寒星

    受教了。

    # re: C#中Finalize方法的问题 2005-1-13 21:20 开心就好

    我喜欢这样的交流:)

    # re: C#中Finalize方法的问题 2005-1-14 1:03 lonelystranger

    # re: C#中Finalize方法的问题 2005-1-14 9:08 Ninputer

    好啊,看来我抛的砖头引来了不少美玉啊,呵呵
    看来该有人写写VB的书说说这些情况,好让VB程序员用好.NET语言中唯一可以自由使用的Finalize。

    # re: C#中Finalize方法的问题 2005-1-14 9:13 Ninputer

    在VB里更可以用Shadows将基类的Finalize彻底掩盖掉,然后在其后续子类中就没有原来那个Object的Finalize了!但如果CLR对Finalize特别干涉(就像Constructor那样),Finalize的语义就会发生变化,VB的语法就要发生Break的变化,这是很可怕的。所以我们还是在编译器上做这个限制比较安全。

    # re: C#中Finalize方法的问题 2005-2-22 17:53 Ivony

    我想了一会儿,觉得原因也许非常简单:

    所有的Finalize()方法都是这个样子:

    protected override Finalize()
    {
    //析构
    base.Finalize();
    }

    然后,最重要的是GC是这样调用Finalize():
    ((object) obj).Finalize()

    出现第一种情况是因为:
    Parent覆盖了默认的Finalize(),但这对继承链没有造成任何影响!!Finalize在这里形成了两个分支:
    protected override GrandPapa.Finnalize() {}// (A)
    protected override Parent.Finalize() {}//这个方法不存在,但这并不会影响虚方法链。(B)
    protected override Son.Finalize() { .... }//省略内容。(C)

    protetced new Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//new修饰符被作者省略了。(D)

    GC开始调用Finalize方法,因为ABC都是在object.Finalize()这个虚方法链上的,所以GC会先调用C方法。
    然后,重要的分支出现了,因为D覆盖了B,所以C中的base.Finalize()是D方法!而D并未使用base.Finalize向上传播,所以执行到这里截止了。



    那么,第二种情况也很好解释了,第二种情况的继承链是:
    protected override GrandPapa.Finnalize() {}// (A)
    protected override Parent.Finalize() {}//不存在的方法(B)

    protetced virtual Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//这里同样省略了new修饰符。(D)
    protected override Son.Finalize() { .... }//省略内容。(C)

    哈哈,现在知道为什么出现了作者百思不得其解的问题了吧,第二种情况由于Parent.Finalize有了virtual修饰,覆盖掉原来的override Parent.Finalize(),所以下面的override Son.Finalize()被继承到了virtual Parent.Finalize()下面。
    结果导致GC先执行那个不存在的B方法,然后执行A方法。。。。

    # re: C#中Finalize方法的问题 2005-2-23 15:20 李建忠

    To Ivony,这个解释比较牵强,而且很多地方都有漏洞:)

    1。首先((object) obj).Finalize()这样的说法就是错误的,对于虚方法来讲:

    obj.Finalize()和 ((object) obj).Finalize()的调用是完全一样的,不存在任何区别。这本身就是多态应有之意。

    2。“因为D覆盖了B,所以C中的base.Finalize()是D方法”

    这句话说得没道理。实际上第一种情况的错误无非是C#将Finalize和~Parent()划了等号,结果是定义
    protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
    相当于定义了一个~Parent,只不过编译器没有在我们自己写的Finalize中插入base.Finalize的调用。

    3。“第二种情况由于Parent.Finalize有了virtual修饰,覆盖掉原来的override Parent.Finalize()”

    这个完全不叫覆盖(override),而叫隐藏(hide),注意其中编译后的元数据有一个newslot——也就是一个新的虚表slot。

    4。“所以下面的override Son.Finalize()被继承到了virtual Parent.Finalize()下面。”

    抱歉,听不懂这句话的意思。


    5。“结果导致GC先执行那个不存在的B方法”

    不存在如何执行呢?

    # re: C#中Finalize方法的问题 2005-2-23 17:49 Ivony

    1。首先((object) obj).Finalize()这样的说法就是错误的,对于虚方法来讲:

    obj.Finalize()和 ((object) obj).Finalize()的调用是完全一样的,不存在任何区别。这本身就是多态应有之意。

    ------------------------------------------

    的确是这样的,只需要:
    object obj = xxxx;
    然后obj.Finalize();就是执行object继承链上的Finalize方法,写一个强制类型转换只不过是为了强调GC在调用Finalize方法的时候,是对一个object类型的对象调用的。



    2。“因为D覆盖了B,所以C中的base.Finalize()是D方法”

    这句话说得没道理。实际上第一种情况的错误无非是C#将Finalize和~Parent()划了等号,结果是定义
    protected void Finalize(){ Console.WriteLine("Parent.Finalize");}
    相当于定义了一个~Parent,只不过编译器没有在我们自己写的Finalize中插入base.Finalize的调用。

    -----------------------------------------

    不是这样的,即使你写了Finalize方法,默认的Finalize方法依然存在,这就是所谓的隐藏掉了基类的方法。然后Parent的子类在调用base.Finalize()的时候,就会调用你写的那个,而不会导致原有默认的Finalize方法丢失。



    3。“第二种情况由于Parent.Finalize有了virtual修饰,覆盖掉原来的override Parent.Finalize()”

    这个完全不叫覆盖(override),而叫隐藏(hide),注意其中编译后的元数据有一个newslot——也就是一个新的虚表slot。

    ----------------------------------------

    嗯,微软是叫隐藏,反正就是这个意思了。



    4。“所以下面的override Son.Finalize()被继承到了virtual Parent.Finalize()下面。”

    抱歉,听不懂这句话的意思。

    --------------------------------------

    这里不知道怎么回事把我用于排版的空格给弄没了,我再发一下继承链(override链)。

    第一种情况
    protected override GrandPapa.Finnalize() {}//(A)
    __protected override Parent.Finalize() {}//(B)虽然不存在,但仍是override链的一环
    ____protected override Son.Finalize() { .... }//(C)这个方法override掉了上面的(B),但其base.Finalize却是下面的(D)。

    protetced new Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//(D),这个方法没有override任何方法,不在override链中。

    第二种情况
    protected override GrandPapa.Finnalize() {}// (A)
    __protected override Parent.Finalize() {}//(B)

    protetced virtual Parent.Finalize() { Console.WriteLine("Parent.Finalize");}//(D)
    __protected override Son.Finalize() { .... }//(C)

    关键在这里:
    class Son
    {
    __protected override Finalize()//这里是override掉了(D)而不是(B)。
    }




    5。“结果导致GC先执行那个不存在的B方法”
    不存在如何执行呢?

    我们可以把情况想成这样,当一个类没有实现基类的virtual方法时,编译器会变出一个默认的方法来维系这个override链(当然,只是假设):
    [code]
    override return_type Method( params )
    {
    return base.Methos( params );
    }
    [/code]
    强调执行不存在的B方法只是为了说明override链。
    方法虽然不存在,但在描述override链的时候,必须写一下,否则override链就断了。

    # re: C#中Finalize方法的问题 2005-2-23 18:00 Ivony

    总结,当运行时多态的时候,系统会沿着override链条(上面的确是没说清楚,不应该用继承链这个词)寻找链条终点上的方法执行。而并不是将这个对象还原成最原始的面目(Son)进行调用。


    C#使用方法的签名来判断一个方法。

    当出现上文中第一种情况时,由于两个方法的签名并不相同,一个是sealed的,一个是virtual的,所以override链并不会被破坏,系统找到了正确的方法进行执行。

    然而在上文中第二种情况时,由于两个方法的签名完全一样,都是virtual的,所以override链在这里被打断,Son其实是override作者在Parant类中新定义的方法。



    发现我的真的说不好,干脆待会儿我发幅图片上来。。。

    # re: C#中Finalize方法的问题 2005-2-23 18:10 李建忠

    1. 我理解你的意思,但是你的表述很容易让人误解。我想更准确的说法是:

    GC调用的是一个类型中虚表slot上第一个Finalize方法。

    只有在newslot——也就是一个类型中可能存在多个同名的虚表(new virtual)——时,才存在obj.Finalize()和 ((object) obj).Finalize()调用的区别。

    2。“不是这样的,即使你写了Finalize方法,默认的Finalize方法依然存在,这就是所谓的隐藏掉了基类的方法”

    这不是隐藏(hide),而是实实在在的override——在同一个虚表slot上的override。

    3。我们尽量用英文术语来讨论,不然我很快就被你搞晕了,呵呵:)

    4。我猜出你实际上在叙述虚表slot的问题,尤其是有了一个newslot。但是你的文字表述很难让人看懂。

    5。我们把“override链”还是说成虚表slot比较准确,也容易讨论。

    # re: C#中Finalize方法的问题 2005-2-23 18:38 Ivony

    还有一个地方,也就是第一种情况:

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

    这里没有override关键字,所以应该不是override。。
    我想写一个程序来验证我的想法。待会儿把结果发上来。



    抱歉,因为我对于系统到底是怎么实现虚方法的了解太少了,只是凭感觉把自己的想法说了出来,所以很多地方都不是专业名词,把大家给弄糊涂了。。。。

    # re: C#中Finalize方法的问题 2005-2-23 18:38 李建忠

    To Ivony,

    你的总结不错,但我想更精炼、更准确的总结应该是这样(可以在Rotor的源代码中得到印证):

    GC在调用Finalize方法时选择的是第一个虚表slot上的Finalize方法(也就是Object当初定义的那个虚表slot上的Finalize方法)。

    而第一种情况始终位于同一个虚表slot上。第二种情况则从class Parent开始引入了一个新的newslot。



    很喜欢这样的讨论,欢迎常来:)

    # re: C#中Finalize方法的问题 2005-2-23 18:47 李建忠

    To Ivony,

    这里我错了,确实不是override,虽然有的override并不需要override关键字。protected void Finalize()只是“欺骗”了C#编译器,虽然它有一个警告。Thanks。

    # re: C#中Finalize方法的问题 2005-2-23 19:08 Ivony

    将Son中的析构函数取掉后,验证了我的想法,Parent.Finalize没有被执行,可以肯定Parent.Finalize没有override了。

    # re: C#中Finalize方法的问题 2005-2-23 19:21 Ivony

    这应该是微软的一个失误,可能是设计人员将base.Finalize想当然的看成了被override掉的那个Finalize方法。而base.Method并不一定是你override掉的方法,这是C#里面的一个陷阱,也让C#引入new修饰符后变得复杂。

    有意思的是,在一个类里面不能同时写Finalize和析构函数,编译器会提示说已经存在了一个相同签名的Finalize方法。哈哈。。。。

    # re: C#中Finalize方法的问题 2005-2-25 13:46 Ivony

    再次出现古怪的现象:

    public class Parent : Grandpapa , IFinalize
    {
    // ~Parent(){ Console.WriteLine("Parent.~Parent");}
    #region IFinalize 成员

    public void Finalize()
    {
    Console.WriteLine("Parent.IFinalize.Finalize");
    }

    #endregion
    }


    这段代码的执行结果是抛出异常:

    未处理的“System.TypeLoadException”类型的异常出现在 未知模块 中。

    其他信息: 方法实现中引用的声明不能是 final 方法。类型:TestFinalize.Son,程序集:ConsoleApplication1, Version=1.0.1882.24453, Culture=neutral, PublicKeyToken=null。

    # re: C#中Finalize方法的问题 2005-2-25 13:51 Ivony

    原因可能是在Son中会自动生成一个 override Finalize()方法,而编译器认为这个Finalize方法是override Parent.IFinalize.Finalize的,所以又报错。。。。但这发生在运行期。。。。

    # re: C#中Finalize方法的问题 2005-2-28 13:09 李建忠

    IFinalize是一个什么接口呢?

    # re: C#中Finalize方法的问题 2005-2-28 13:21 李建忠

    如果是一个自己定义的IFinalize接口:
    interface IFinalize
    {
    void Finalize();
    }

    我并没有遇到你所说的问题,能详细讲一下吗?

    # 对不起,发得匆忙,代码没发全。 2005-3-1 18:29 Ivony

    using System;

    namespace TestFinalize
    {

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

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


    public interface IFinalize
    {
    void Finalize();
    }


    public class App
    {
    [STAThread]
    public static void Main()
    {
    Son s=new Son();
    s = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Console.ReadLine();
    }
    }
    }


    顺便说一下,我是在.NET Framework 1.1的环境下测试的,2.0还没有测试,因为据说2.0允许在子类中重新实现接口了。

    运行结果:

    未处理的“System.TypeLoadException”类型的异常出现在 未知模块 中。

    其他信息: 方法实现中引用的声明不能是 final 方法。类型:TestFinalize.Son,程序集:ConsoleApplication1, Version=1.0.1886.32955, Culture=neutral, PublicKeyToken=null。

    # re: C#中Finalize方法的问题 2005-3-2 12:09 Ivony

    将程序稍作改动,得到了另一个结果


    public void Finalize()

    改为:

    public virtual void Finalize() //强令子类能够覆盖



    结果:抛出异常。这一次是子类在重写方法的时候不能降低访问级别。。。。但奇怪的是,所有的这些编译时就该出现的问题都是到了程序的运行期抛出异常。。。。

    # re:C#中Finalize方法的问题 2005-4-10 20:28 实验室家具

    ^_^,Pretty Good!

    # re:C#中Finalize方法的问题 2005-4-16 17:17 拉力试验机

    ^_~

    # re:C#中Finalize方法的问题 2005-7-17 11:40 红外热像仪

    C#中Finalize方法的问题ooeess

    # re:C#中Finalize方法的问题 2005-8-1 18:07 红外热像仪

    C#中Finalize方法的问题ooeess

    # re: C#中Finalize方法的问题 2005-11-13 16:08 Farseer

    我想您误会了Anders 的意思了。在《C# Programming Language》的10.12 Destructors一节中Anders是这样说的
    Destructors are implemented by overriding the virtual method Finalize on System.Object. C# programs are not permitted to override this method or call it (or overrides of it) directly. For instance, the program
    class A
    {

    override protected void Finalize() {} // error

    public void F() {

    this.Finalize(); // error

    }

    }
    contains two errors.
    The compiler behaves as if this method, and overrides of it, do not exist at all。Thus, this program
    class A
    {
    void Finalize() {} // permitted

    }
    is valid, and the method shown hides System.Object's Finalize method.
    从前后文来看,Anders所指的as if the method中的method特指的是object的Finalize方法。而不是您自己所定义的Finalize方法,而Anders后面所举的例子也正如您说的,自己定义的Finalize方法,hide了System.Object's Finalize方法。

    # re: C#中Finalize方法的问题 2005-11-13 17:01 Farseer

    只是Anders没有明确说当hides ojbect这个Finalize方法的方法是个virtual的时候该怎么办。
    当没有virtual这个modified notation的时候,Parent 中的Finalize方法hide了obejct的Finalize方法,但还有析构语意,所以在GC.Collect()还会被调用。
    由于出现了一个new slot,Finalize在Parent又开辟了一个继承链体系,跟ojbect的Finalize方法已经没有任何关系了。Finalize方法在Parent中已经变成了没有析构语意的普通方法,当然~son也就是overidding的也是这个没有任何析构语意的Finalize方法,本身也就没有了析构语意。既然没有析构语意,当GC.Collect()的时候按照常理也不应该被调用的。

  • 相关阅读:
    How to convert VirtualBox vdi to KVM qcow2
    (OK)(OK) adb -s emulator-5554 shell
    (OK)(OK) using adb with a NAT'ed VM
    (OK) How to access a NAT guest from host with VirtualBox
    (OK) Creating manually one VMs from an existing VDI file in CLI (VBoxManage) in Fedora 23
    (OK)(OK) Creating VMs from an existing VDI file in CLI (VBoxManage) in Fedora 23
    (OK) Creating_VMs_from_an_existing_VDI_file.txt
    (OK) Creating VMs from an existing VDI file —— in OS X
    (OK) install_IBM_SERVER.txt
    (OK) install chrome & busybox in android-x86_64 —— uninstall chrome
  • 原文地址:https://www.cnblogs.com/cxd4321/p/533265.html
Copyright © 2011-2022 走看看