C# - “析构函数不被继承”是什么意思?实际上是什么意思?

发布于 2024-08-14 04:58:32 字数 2292 浏览 2 评论 0原文

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# 程序不允许重写或调用此方法 直接(或覆盖它)。

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

Section 10.13, Destructors, of the C# Language Specification 3.0 states the following:

Destructors are not inherited. Thus, a class has no destructors other than the one which may be declared in that class.

The Destructors section of the C# Programming Guide contains an example demonstrating how destructors in an inheritance hierarchy are called, including the following statement:

...the destructors for the ... classes are called automatically, and in order, from the most-derived to the least-derived.

I have investigated this with various practical examples, including one with a base class that defines a destructor, with a derived class that inherits from the base class and does not define a destructor. Creating an instance of the derived class, allowing all references to the instance to go out of scope and then forcing a garbage collection demonstrates that the destructor defined in the base class is called when the instance of the derived class is finalized.

My question is what does "destructors are not inherited" actually mean, since although you can't call a destructor explicitly, destructors in an inheritance chain are called automatically, and base class destructors are called even if the derived class does not define a destructor?

Does it relate to some subtle semantic distinction that finalization is implemented by the garbage collector rather than the C# language/compiler?

Edit 1:

While the C# language spec also states that "instance constructors are not inherited", the behaviour in relation to constructors is significantly different from desctructors, and fits better IMO with the "not inherited" terminology, as demonstrated in the example below:

  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"); 

The behaviour in relation to destructors is very different from this, as demonstrated in the following example, which extends the previous constructor example by adding a desctructor only to the base class.

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

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

  ...

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

The example above demonstrates that base class constructors will be called when an instance of a derived class is finalized, even if the derived class does not explicitly define a destructor.

My point is simply that I have encountered many people who do not realise or understand that this what happens, and a significant part of the reason for this is the "destructors are not inherited" statement.

Edit 2:

The C# language spec also states the following and gives a code example of the under-the-hood implementation:

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.

Since the under-the-hood implementation is, in fact, based on inheritance, as stated above, I think my question is valid and I don't think any of the responses I've received so far have addressed the question properly - What does "destructors are not inherited" actually mean?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

番薯 2024-08-21 04:58:32

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

抛开这一点,一次一个地考虑你的问题:

我的问题是“析构函数”是什么意思
不是遗传的”实际上意味着,
因为虽然你不能打电话
显式析构函数,析构函数在
一个继承链被称为
自动和基类
即使析构函数被调用
派生类没有定义
析构函数?

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

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

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

没有。

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

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

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

虽然 C# 语言规范也指出
“实例构造函数不是
继承”,相关行为
对建设者来说意义重大
与析构函数不同,并且适合
IMO 更好的“不继承”
术语

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

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

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

我的观点很简单,我有
遇到很多不认识的人
意识到或理解这是什么
发生,并且很大一部分
原因是“析构函数
不被继承”声明。

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

自从在幕后
实施实际上是基于
继承,如上所述,我认为
我的问题是有效的

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

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

类似地,我们的 C# 实现通过将 IL 注入到重写名为 Finalize 的虚拟方法的方法中来实现析构函数。 C# 语言经过精心设计,不依赖于运行时的此功能。 C# 的另一种实现可以完全自由地选择其他一些机制来实现析构函数。 (对于我们来说,修改规范以使其更清楚地了解析构函数如何实现是一个信息丰富的实现细节,而不是 C# 语言的要求。)

但无论实现细节如何选择, 这可能是一个好主意。 ,基类中的析构函数不是派生类的成员。正如,无论实现细节如何,匿名类型都没有名称。

现在清楚了吗?或者您还有其他问题吗?

It's not a question of winning. I'm trying my best to understand your questions, but you seem preoccupied with defending yourself against entirely imaginary attacks.

Leaving that aside, and considering your questions one at a time:

My question is what does "destructors
are not inherited" actually mean,
since although you can't call a
destructor explicitly, destructors in
an inheritance chain are called
automatically, and base class
destructors are called even if the
derived class does not define a
destructor?

It actually means that destructors of a base class are not members of a derived class.

My counter-question is "why do you believe that the facts that (1) you cannot call a destructor directly, (2) destructors in an inheritance chain are called automatically upon collection, and (3) base class destructors are called even if the derived class does not define a constructor, have any bearing whatsoever on the question of whether or not a destructor of a base class is a member of a derived class?

Does it relate to some subtle semantic
distinction that finalization is
implemented by the garbage collector
rather than the C# language/compiler?

Nope.

The C# language is a specification; it implements nothing. The C# compiler implements the specification; it certainly does not "implement finalization". The CLR is what implements finalization.

Regardless of any of that, the fact that destructors in a base class are not members of a derived class has nothing whatsoever to do with how finalization is implemented by the CLR garbage collector.

We'll return to the question of CLR finalization semantics, and why they are not relevant to the question of inheritance, at the end.

While the C# language spec also states
that "instance constructors are not
inherited", the behaviour in relation
to constructors is significantly
different from desctructors, and fits
better IMO with the "not inherited"
terminology

OK, I accept that you believe that. Why do you believe that these behaviours are relevant to the question of inheritance? I'm not following in the slightest why you believe that. What's relevant to inheritance is whether the entity in question is a member of the derived type simply by virtue of it being a member of the base type.

I agree that the fact that a constructor for a base type cannot be invoked directly via a construction of a derived type which lacks that constructor is consistent with the fact that constructors are not inherited. I just don't see how this is germane to the question of whether destructors are inherited.

A more germane fact is that a base class with a public parameterless constructor DOES have that constructor invoked when a derived class which lacks a public parameterless constructor is constructed with its default constructor. The derived class does not inherit the base class's public parameterless constructor, but it is called nevertheless. If you accept that a constructor, not inherited, can be invoked in this way, then why not accept that a destructor is also invoked with similar semantics?

My point is simply that I have
encountered many people who do not
realise or understand that this what
happens, and a significant part of the
reason for this is the "destructors
are not inherited" statement.

I agree completely. One probably ought not to write destructors if one does not have a clear understanding of exactly what the correct semantics are. I am dismayed by the number of books for C# beginners which treat destructors as a suitable topic for novices; writing a correct destructor is one of the hardest basic tasks in C#.

Since the under-the-hood
implementation is, in fact, based on
inheritance, as stated above, I think
my question is valid

Your question is perfectly valid regardless. But now you are conflating the implementation details of a feature with the specification of the feature.

By analogy, consider anonymous types. Reasonably enough, they don't have names. But the metadata we spit of course names the type; the CLR requires types to have names. Is that a contradiction? Not really. The conceptual entity called "anonymous type" by the specification does not have a name, and that's what matters. An implementor is of course free to use a platform that does not require naming of all types.

Similarly, our implementation of C# implements destructors by spitting IL into a method that overrides a virtual method called Finalize. The C# language has been carefully designed to not take a dependency upon this feature of the runtime. Another implementation of C# is perfectly free to choose some other mechanism to implement destructors. (It might be a good idea for us to modify the spec to make it more clear that the bit about how destructors are implemented is an informative implementation detail, and not a requirement of the C# language.)

But regardless of the choice of implementation details, destructors in a base class are not members of a derived class. Just as, regardless of implementation details, anonymous types do not have names.

Is that now clear, or do you have more questions?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文