37

请注意:这个问题是关于“析构函数”和“终结器”这两个词之间的术语差异及其正确用法。我只是提供了它们在 C# 和 C++/CLI 中的使用示例,以说明我问这个问题的原因。我很清楚它是如何在 C# 和 CLR 中实现的,但我问的是术语的正确使用。


在 C# 世界中,术语“析构函数”和“终结器”似乎可以互换使用,我怀疑这是因为 C# 规范使用“析构函数”一词描述了非确定性清理功能,而 CLR 文档总是使用“终结器”一词,因此在 C# 领域内,它们的含义相同。

但是,在 C++/CLI 规范中,两者之间存在区别。它允许确定性和非确定性清理,并使用术语“析构函数”表示确定性功能,使用“终结器”表示非确定性功能:

终结器提供非确定性清理。终结器是在垃圾回收期间执行的“最后机会”函数,通常在未执行析构函数的对象上执行。

此外,维基百科对析构函数终结器的描述表明析构函数和终结器是独立的概念,并支持 C++/CLI 规范对确定性术语的使用:

与析构函数不同,终结器不是确定性的。当程序显式释放对象时,将运行析构函数。相比之下,终结器在内部垃圾回收系统释放对象时执行。

问题:

  • 从计算机科学的角度来看,“析构函数”和“终结函数”之间是否存在明确定义的区别,或者术语只能根据上下文来定义?

  • 如果存在明确定义的差异,那么为什么 C# 规范会使用“错误”的术语?

4

4 回答 4

40

1)工业或学术界使用的“析构函数”和“终结器”之间是否存在明确的区别?

肯定有。区别似乎在于析构函数是确定性调用的清理方法,而终结器在垃圾收集器告诉它们时运行。

2) 在这种情况下,C# 规范弄错了——终结器在 C# 中被称为“析构函数”。为什么 C# 规范的作者弄错了?

我不知道,但我能猜到。事实上,我有两个猜测。

猜测 #1 是在 1999 年 5 月 12 日,没有一篇维基百科文章清楚地描述这两个概念之间的细微差别。那是因为没有维基百科。还记得没有维基百科的时候吗?黑暗时代,伙计。该错误可能只是一个诚实的错误,认为这两个术语是相同的。

哎呀,据我所知,这两个术语在 1999 年 5 月 12 日相同的,并且定义上的差异只是在后来才发展起来的,因为很明显需要消除急切和惰性清理方法之间的歧义。

猜测 #2 是 1999 年 5 月 12 日,语言设计委员会希望保留“析构函数”可以作为终结器以外的其他东西实现的可能性。也就是说,“析构函数”被设计为 C# 语言概念,不一定与 .NET“终结器”概念一一对应。在设计一种语言的同时,它所在的框架也在设计中,有时您希望将自己与子系统中最新的设计更改隔离开来。

语言委员会 1999 年 5 月 12 日的部分说明如下:

我们将为在实例被回收时执行的成员使用术语“析构函数”。类可以有析构函数;结构不能。与 C++ 不同,析构函数不能被显式调用。析构是非确定性的——你不能可靠地知道析构函数什么时候执行,除非说它在对对象的所有引用都被释放后的某个时刻执行。继承链中的析构函数按顺序调用,从最多的后代到最少的后代。派生类没有必要(也没有办法)显式调用基析构函数。C# 编译器将析构函数编译为适当的 CLR 表示。对于这个版本,这可能意味着在元数据中区分的实例终结器。CLR 将来可能会提供静态终结器;我们没有看到 C# 使用静态终结器的任何障碍。

所以,在那里,你现在知道我在这个问题上所知道的一切。如果您想了解更多信息,请在下次见到安德斯时询问他。

于 2009-12-09T16:40:44.253 回答
6

严格来说,C# 没有析构函数(我相信 C# 规范在这一点上令人困惑)。C# 的终结器看起来像 C++ 析构函数,但正如您所说,它是不确定的。C# 具有以下形式的确定性清理IDisposable::Dispose(但这仍然不称为析构函数)。

C++/CLI 具有确定性析构函数,看起来像 C++ 析构函数。在 CLI 级别,这些映射到IDisposable::Dispose()IDisposable为您实现)。C++/CLI 有非确定性终结器,看起来像析构函数,但使用 ! 前缀而不是 ^ 前缀。

C++/CLI 使用与 C# 用于终结器的析构函数相同的语法这一事实可能有点令人困惑 - 但它更适合 C++,它具有确定性破坏的强大传统。

不幸的是,这些术语缺乏通用定义意味着你总是需要澄清你在说什么。就我个人而言,在谈论 C# 概念时,我总是使用finalizer这个词,并为它肯定意味着确定性破坏的上下文保留析构函数。

[更新] 自从我在这里发表原始帖子以来,Eric Lippert 已经发布了他的回复(已接受的答案),并随后发布了一篇博文。我很高兴看到我们达成一致:-)

于 2009-12-09T09:48:13.693 回答
4

我相信“析构函数”是 C# 代码,“终结器”是编译后的 CIL 方法。C# 编译器将析构函数转换为终结器。

编辑:更冗长;C# 语言规范将“析构函数”定义为类上的 C# 实例方法。因此,“析构函数”是 C# 语法的一部分——析构函数是出现在源代码中的语言对象。

析构函数是实现析构类实例所需操作的成员。析构函数不能有参数,不能有可访问性修饰符,也不能显式调用。实例的析构函数在垃圾回收期间自动调用。

“终结器”是公共语言运行时中使用的术语,例如在对 的调用中GC.WaitForPendingFinalizers(),它指的是任何 .net 语言,而不仅仅是 C#。任何 .net 引用类型都可以有一个终结器。对于 C# 类(源代码工件),析构函数编译为 CIL 方法,它是 CLR 类型的终结器。

或者更简洁地说,析构函数终结器,就像源代码机器代码一样;)

于 2009-12-09T09:43:39.217 回答
3

如果我们遵守析构函数的“确定性”定义,那么我会说 .NET 中的对象没有析构函数,除非使用IDisposable 接口显式实现。

于 2009-12-09T09:46:52.880 回答