什么时候应该创建析构函数?


185

例如:

public class Person
{
    public Person()
    {
    }

    ~Person()
    {
    }
}

什么时候应该手动创建析构函数?您何时需要创建析构函数?


1
C#语言将它们称为“析构函数”,但大多数人将它们称为“ finalizers”,因为这是它们的.NET名称,它减少了与C ++析构函数(两者有很大不同)的混淆。如何实现IDisposable和Finalizer:3条简单规则
Stephen Cleary

5
当你感到鲁re。
capdragon 2011年

32
我认为TomTom的键盘有故障。大写锁定偶尔会打开和关闭。奇怪的。
杰夫·拉法


根据Greg Beech的建议,我最终使用析构函数作为调试辅助工具:stackoverflow.com/questions/3832911/…–
Brian

Answers:


237

更新:这个问题是我2015年5月的博客主题。感谢您提出的好问题!请参阅博客,以获取人们通常相信的关于终结的虚假列表。

什么时候应该手动创建析构函数?

几乎从不。

通常,只有当您的类持有一些昂贵的非托管资源时才创建一个析构函数,该资源在对象消失时必须清除。最好使用一次性模式以确保清理资源。那么从本质上讲,析构函数可以确保,如果对象的使用者忘记处理该对象,则该资源最终仍会被清除。(也许。)

如果您要使析构函数非常小心,请了解垃圾收集器的工作原理。析构函数真的很奇怪

  • 它们不在您的线程上运行;他们在自己的线程上运行。不要造成僵局!
  • 从析构函数抛出的未处理异常是个坏消息。它在自己的线程上;谁去抓呢?
  • 析构函数可以在对象上调用之后的构造开始,但之前构造完成。正确编写的析构函数将不依赖于在构造函数中建立的不变式。
  • 析构函数可以“复活”一个对象,使死掉的对象再次存活。真的很奇怪 不要这样
  • 析构函数可能永远不会运行。您不能依赖曾经计划完成的对象。它可能会,但是这不能保证。

在析构函数中,通常没有什么是正确的。要非常非常小心。编写正确的析构函数非常困难。

您何时需要创建析构函数?

测试编译器中处理析构函数的部分时。我从来不需要在生产代码中这样做。我很少编写处理非托管资源的对象。


“在构造函数开始之后但构造函数完成之前,可以在对象上调用析构函数。” 但是我可以依靠字段初始化程序来运行,对吗?
配置器

13
@configurator:不。假定对象的第三个字段初始化器带有一个称为静态方法的终结器,该终结器会引发异常。第四个字段初始化器什么时候运行?决不。但是对象仍然被分配并且必须完成。哎呀,您甚至无法保证在dtor运行时对double 类型的字段进行了完全初始化。在编写double的过程中可能有线程中止的情况,现在终结器必须处理一个半初始化的half-zero double。
埃里克·利珀特

1
出色的文章,但应该说“应该在您的类持有一些昂贵的非托管对象或导致存在大量非托管对象时创建”-具体而言,我在C#中有一个使用基础本机C ++的矩阵类矩阵类要做很多繁重的工作-我做了很多矩阵-在这种特定情况下,“析构函数”优于IDisposable,因为它使房子的托管和非托管双方保持更好的同步
Mark Mullin

1
pythonnet使用析构函数在不受管理的CPython中释放GIL
denfromufa 2015年

3
很棒的文章Eric。的建议->“额外的乐趣:在调试器中运行程序时,运行时使用较少的主动代码生成和较少的主动垃圾回收,因为即使您正在调试的对象突然消失,调试体验也很差。引用对象的变量在范围内。这意味着,如果您有一个过早完成对象的错误,则可能无法在调试器中重现该错误!”
肯·帕尔默

17

它被称为“ finalizer”,通常只应为状态(即字段)包含非托管资源(即:指向通过p / invoke调用检索到的句柄的指针)的类创建一个。但是,在.NET 2.0和更高版本中,实际上有一种更好的方法来处理非托管资源的清理:SafeHandle。鉴于此,您几乎永远不必再编写终结器。


25
@ThomasEding- 是的。C#使用析构函数语法,但实际上是在创建一个finalizer再来一次
JDB仍记得Monica

@JDB:语言构造称为析构函数。我不喜欢这个名字,但这就是它的名字。声明析构函数的行为使编译器生成一个finalizer方法,该方法包含一些包装器代码以及析构函数主体中出现的所有内容。
超级猫

8

除非您的班级维护非托管资源(例如Windows文件句柄),否则您不需要一个。


5
好吧,实际上,它被称为析构函数
David Heffernan

2
现在我很困惑。是终结器还是析构函数?

4
实际上,C#规范确实将其称为析构函数。有些人认为这是一个错误。stackoverflow.com/questions/1872700/…–
阿尼

2
@ThomasEding- 是的。C#使用析构函数语法,但实际上是在创建一个finalizer
JDB仍记得Monica

2
我喜欢这里的评论,真正的panto :)
Benjol 2015年

4

它称为析构函数/终结器,通常是在实现Disposed模式时创建的。

当类的用户忘记调用Dispose来确保(最终)释放资源时,这是一个后备解决方案,但是您不能保证何时调用析构函数。

在这个Stack Overflow问题中,被接受的答案正确地显示了如何实现处置模式。仅当您的类包含垃圾处理器无法自行清理的任何未处理资源时,才需要使用此方法。

一个好的实践是在没有给类用户提供手动分配对象以立即释放资源的可能性的情况下,不要实现终结器。


实际上,由于充分的理由,它在C#中没有被称为析构函数。
TomTom

14
其实。感谢您给我下一张选票,因为您弄错了。见关于这个具体问题上的MSDN Library:msdn.microsoft.com/en-us/library/66x5fx1b.aspx
岛之风Bråthen如此阐述

1
@TomTom的正式名称是析构函数
David Heffernan

它实际上不是后备方法,它仅允许GC管理对象何时释放非托管资源,实现IDisposable则允许您自己进行管理。
HasaniH 2011年

3

当您拥有非托管资源时,您需要确保在对象消失时将其清除。一个很好的例子是COM对象或文件处理程序。


2

我已经使用了析构函数(仅用于调试目的)来查看是否从WPF应用程序范围内的内存中清除了对象。我不确定垃圾回收是否真的从内存中清除了对象,这是验证的好方法。


1

析构函数提供了一种隐式方式来释放封装在您的类中的非托管资源,它们在GC到达时被调用,并且隐式调用基类的Finalize方法。如果您正在使用大量非托管资源,则最好提供一种明确的方式,通过IDisposable接口释放那些资源。请参阅C#编程指南:http : //msdn.microsoft.com/zh-cn/library/66x5fx1b.aspx

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.