C# - "析构函数不被继承"是什么意思?

Sec*_*rel 13 .net c# destructor idisposable finalization

C#语言规范3.0的第10.13节"析构函数"声明如下:

析构函数不是继承的.因此,除了可以在该类中声明的类之外,类没有析构函数.

" C#编程指南"的 "Destructors"部分包含一个示例,演示如何调用继承层次结构中的析构函数,包括以下语句:

... ...类的析构函数是自动调用的,按顺序,从最大派生到最小派生.

我用各种实际例子对此进行了调查,其中包括一个定义析构函数的基类,一个派生类,它继承自基类,并没有定义析构函数.创建派生类的实例,使该实例的全部引用走出去的范围,然后迫使垃圾收集表明,当派生类的实例被敲定在基类中定义的析构函数被调用.

我的问题是"析构函数不是继承的"实际意味着什么,因为虽然你不能显式地调用析构函数,但是继承链中的析构函数会被自动调用,并且即使派生类没有定义析构函数,也会调用基类析构函数?

是否与垃圾收集器而不是C#语言/编译器实现的最终化有些微妙的语义区别?

编辑1:

虽然C#语言规范还声明"实例构造函数不是继承的",但与构造函数相关的行为与析构函数明显不同,并且更符合IMO与"未继承"的术语,如下例所示:

  public class ConstructorTestBase
  {
    public ConstructorTestBase(string exampleParam)
    {
    }
  }

  public class ConstructorTest: ConstructorTestBase
  {
    public ConstructorTest(int testParam)
      : base(string.Empty)
    {
    }
  }

  ...

  // The following is valid since there is a derived class constructor defined that
  // accepts an integer parmameter.
  ConstructorTest test1 = new ConstructorTest(5);

  // The following is not valid since the base class constructor is not inherited
  // by the derived class and the derived class does not define a constructor that
  // takes a string parameter.
  ConstructorTest test2 = new ConstructorTest("Test"); 
Run Code Online (Sandbox Code Playgroud)

与析构函数相关的行为与此非常不同,如以下示例所示,后者通过仅向基类添加析构函数来扩展先前的构造函数示例.

  public class ConstructorTestBase
  {
    public ConstructorTestBase(string exampleParam)
    {
    }

    ~ConstructorTestBase()
    {
      Console.WriteLine("~ConstructorTestBase()");
    }
  }

  ...

  ConstructorTest test1 = new ConstructorTest(5);
  test1 = null;
  GC.Collect();
Run Code Online (Sandbox Code Playgroud)

上面的示例演示了在派生类的实例完成时将调用基类构造函数,即使派生类没有显式定义析构函数也是如此.

我的观点很简单,我遇到了许多人没有意识到或明白发生这种情况,其中很大一部分原因就是"析构函数不是继承"的说法.

编辑2:

C#语言规范还说明了以下内容,并给出了底层实现的代码示例:

通过覆盖System.Object上的虚拟方法Finalize来实现析构函数.不允许C#程序覆盖此方法或直接调用它(或覆盖它).

由于在引擎罩实施,事实上,根据产业,正如上文所述,我认为我的问题是有效的,我不认为任何我已经收到到目前为止已经妥善解决问题的答复 - 什么"析构函数不是继承的"实际意味着什么?

Eri*_*ert 19

这不是胜利的问题.我正在尽我所能去理解你的问题,但你似乎专注于保护自己免受完全想象的攻击.

暂且不说,一次考虑一个问题:

我的问题是"析构函数不是继承的"实际意味着什么,因为虽然你不能显式地调用析构函数,但是继承链中的析构函数会被自动调用,并且即使派生类没有定义析构函数,也会调用基类析构函数?

实际上意味着基类的析构函数不是派生类的成员.

我的反问题是"为什么你认为(1)你不能直接调用析构函数,(2)继承链中的析构函数在收集时自动调用,以及(3)基类析构函数被调用,即使派生类没有定义构造函数,对于基类的析构函数是否是派生类的成员的问题有任何影响吗?

是否与垃圾收集器而不是C#语言/编译器实现的最终化有些微妙的语义区别?

不.

C#语言是一个规范; 它什么都不实现.C#编译器实现了规范; 它肯定不会"实现最终确定".CLR实现了最终化.

无论如何,基类中的析构函数不是派生类的成员这一事实与CLR垃圾收集器如何实现最终化没有任何关系.

我们将回到CLR终结语义的问题,以及为什么它们最终与继承问题无关.

虽然C#语言规范还声明"实例构造函数不是继承的",但与构造函数相关的行为与析构函数明显不同,并且更适合IMO与"未继承"术语

好的,我接受你的相信.为什么你认为这些行为与继承问题有关?我没有丝毫跟随你为什么相信这一点.与继承相关的是,所讨论的实体是否是派生类型的成员,仅仅因为它是基类型的成员.

我同意不能通过构造缺少该构造函数的派生类型直接调用基类型的构造函数这一事实与构造函数不被继承的事实一致.我只是没有看到这与析构函数是否继承的问题有何密切关系.

更具有根本性的事实是,当缺省公共无参数构造函数的派生类使用其默认构造函数构造时,具有公共无参数构造函数DOES的基类具有该构造函数.派生类不继承基类的公共无参数构造函数,但仍会调用它.如果您接受可以以这种方式调用未继承的构造函数,那么为什么不接受使用类似语义调用析构函数呢?

我的观点很简单,我遇到了许多人没有意识到或明白发生这种情况,其中很大一部分原因就是"析构函数不是继承"的说法.

我完全同意.如果一个人没有清楚地理解正确的语义是什么,那么人们可能不应该编写析构函数.我对C#初学者的书籍数量感到沮丧,这些书籍将析构函数视为新手的合适主题; 编写正确的析构函数是C#中最难的基本任务之一.

事实上,由于底层实现是基于继承,如上所述,我认为我的问题是有效的

无论如何,你的问题完全有效.但是现在您将功能的实现细节与功能的规范混为一谈.

通过类比,考虑匿名类型.合理地说,他们没有名字.但是我们吐的元数据当然是这个类型的名字; CLR 要求类型具有名称.那是矛盾的吗?并不是的.规范中称为"匿名类型"的概念实体没有名称,这才是最重要的.实现者当然可以自由地使用不需要命名所有类型的平台.

类似地,我们的C#实现通过将IL分配到一个覆盖名为Finalize的虚方法的方法来实现析构函数.C#语言经过精心设计,不依赖于运行时的这一特性.C#的另一个实现完全可以自由选择其他机制来实现析构函数.(对于我们来说,修改规范可能是一个好主意,以便更清楚地说明有关如何实现析构函数的信息是一个信息丰富的实现细节,而不是C#语言的要求.)

但无论实现细节的选择如何,基类中的析构函数都不是派生类的成员.同样,无论实现细节如何,匿名类型都没有名称.

这是现在清楚了,还是你有更多问题?

  • 感谢您的回复.简短的回答似乎是"基类的析构函数不是派生类的成员",而C#的其他实现可能以不同的方式实现析构函数.这非常有帮助.对规范的澄清可能会为其他人带来一些痛苦.编写可移植的C#代码,是从实现IDisposable的基类派生的所有类中都需要的析构函数,以确保释放基类中的非托管资源,而不是依赖于基本析构函数中的调用到虚拟Dispose(bool)处理方法? (2认同)
  • 这个问题是一个危险的假设; 你为什么要编写可以在C#的不同实现中移植的代码来释放非托管资源?当然,释放非托管资源本质上是一种绑定到特定操作系统的操作.C#的实现可以自由地在它选择的任何线程上运行析构函数,延迟运行析构函数,依此类推.尝试编写这样的析构函数对我来说似乎非常危险. (2认同)