13

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

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

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

... ...类的析构函数被自动调用,并且按从最派生到最不派生的顺序。

我已经通过各种实际示例对此进行了研究,包括一个具有定义析构函数的基类,以及从基类继承但未定义析构函数的派生类。创建派生类的实例,允许对该实例的所有引用超出范围,然后强制进行垃圾回收,这表明当派生类的实例最终确定时,将调用基类中定义的析构函数。

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

它是否与一些微妙的语义区别有关,即终结是由垃圾收集器而不是 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"); 

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

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

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

  ...

  ConstructorTest test1 = new ConstructorTest(5);
  test1 = null;
  GC.Collect();

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

我的观点很简单,我遇到过很多人没有意识到或不理解会发生这种情况,其中很大一部分原因是“析构函数不是继承的”声明。

编辑2:

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

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

由于引擎盖下的实现实际上是基于继承的,如上所述,我认为我的问题是有效的,我认为到目前为止我收到的任何回复都没有正确解决这个问题 - 什么“析构函数不是继承的”实际上是什么意思?

4

1 回答 1

19

这不是获胜的问题。我正在尽力理解你的问题,但你似乎全神贯注于保护自己免受完全虚构的攻击。

撇开这些不谈,一次一个地考虑你的问题:

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

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

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

它是否与一些微妙的语义区别有关,即终结是由垃圾收集器而不是 C# 语言/编译器实现的?

没有。

C# 语言是一种规范;它什么也没实现。C#编译器实现规范;它当然不会“实现最终确定”。CLR 是实现终结的东西。

不管怎样,基类中的析构函数不是派生类的成员这一事实与 CLR 垃圾收集器如何实现终结无关。

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

虽然 C# 语言规范还声明“实例构造函数不被继承”,但与构造函数相关的行为与析构函数有很大不同,并且更适合 IMO 与“不继承”术语

好的,我接受你相信这一点。为什么你认为这些行为与继承问题有关?我一点也不理解你为什么相信这一点。与继承相关的是,所讨论的实体是否仅仅因为它是基类型的成员而成为派生类型的成员。

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

一个更密切的事实是,当使用其默认构造函数构造缺少公共无参数构造函数的派生类时,具有公共无参数构造函数的基类确实会调用该构造函数。派生类不继承基类的公共无参数构造函数,但仍会调用它。如果您接受可以以这种方式调用非继承的构造函数,那么为什么不接受也以类似的语义调用析构函数呢?

我的观点很简单,我遇到过很多人没有意识到或不理解会发生这种情况,其中很大一部分原因是“析构函数不是继承的”声明。

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

由于引擎盖下的实现实际上是基于继承的,如上所述,我认为我的问题是有效的

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

以此类推,考虑匿名类型。有道理,他们没有名字。但是我们吐出的元数据当然会命名类型;CLR要求类型具有名称。这是矛盾吗?并不真地。规范中称为“匿名类型”的概念实体没有名称,这很重要。实现者当然可以自由地使用不需要命名所有类型的平台。

类似地,我们的 C# 实现通过将 IL 注入一个覆盖称为 Finalize 的虚拟方法的方法来实现析构函数。C# 语言经过精心设计,不依赖于运行时的这一特性。C# 的另一种实现完全可以自由地选择其他一些机制来实现析构函数。(对我们来说,修改规范以更清楚地说明析构函数的实现方式是一个提供信息的实现细节,而不是 C# 语言的要求,这可能是一个好主意。)

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

现在清楚了吗,还是您还有更多问题?

于 2009-12-09T21:30:43.723 回答