【问题标题】:When should I create a destructor?我应该什么时候创建析构函数?
【发布时间】:2011-06-21 09:19:06
【问题描述】:

例如:

public class Person
{
    public Person()
    {
    }

    ~Person()
    {
    }
}

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

【问题讨论】:

标签: c# destructor


【解决方案1】:

更新:这个问题是the subject of my blog in May of 2015。谢谢你的好问题!请参阅博客,了解人们普遍认为的关于最终确定的一长串谎言。

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

几乎没有。

通常只有当你的类持有一些昂贵的非托管资源时才创建一个析构函数,当对象消失时必须清理这些资源。最好使用一次性模式来确保资源被清理干净。析构函数本质上是一个保证,如果你的对象的消费者忘记释放它,资源最终还是会被清理掉。 (也许。)

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

  • 它们不在您的线程上运行;他们在自己的线程上运行。不要造成死锁!
  • 析构函数引发的未处理异常是个坏消息。它在自己的线程上;谁会抓住它?
  • 可以在之后构造函数开始但构造函数完成之前调用对象的析构函数。正确编写的析构函数不会依赖于构造函数中建立的不变量。
  • 析构函数可以“复活”一个对象,使一个死去的对象再次活跃起来。这真的很奇怪。不要这样做。
  • 析构函数可能永远不会运行;您不能依赖被安排完成的对象。 可能会,但这不是保证。

在析构函数中,几乎所有正常情况下都是真的。非常非常小心。编写正确的析构函数非常困难。

你什么时候需要创建析构函数?

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

【讨论】:

  • @configurator:不。假设对象的第三个字段初始化器带有一个称为静态方法的终结器,该方法导致抛出异常。第四个字段初始化程序何时运行?绝不。但是对象仍然被分配并且必须被最终确定。哎呀,你甚至不能保证 double 类型的字段在 dtor 运行时被完全初始化。在写入 double 的过程中可能有一个线程中止,现在终结器必须处理一个半初始化的半零 double。
  • 优秀的帖子,但应该说“应该在你的类持有一些昂贵的非托管对象或导致大量非托管对象存在时创建”-举一个具体的例子,我有一个矩阵类在 C# 中,它利用底层的原生 C++ 矩阵类来做很多繁重的工作——我制作了很多矩阵——在这种特定情况下,“析构函数”远远优于 IDisposable,因为它保留了房子的托管和非托管方面更好地同步
  • pythonnet 使用析构函数在非托管 CPython 中释放 GIL
  • 很棒的文章埃里克。对此的支持 --> “额外的乐趣:运行时在调试器中运行程序时使用较少激进的代码生成和较少激进的垃圾收集,因为即使您正在调试的对象突然消失也是一种糟糕的调试体验,即使引用对象的变量在范围内。这意味着如果您有一个对象过早完成的错误,您可能无法在调试器中重现该错误!"
  • @KenPalmer 是的,那段中描述的行为对我打击很大。多年来一直在寻找 AccessViolationException 的来源。当然,它只发生在 Release 版本中。当然,它出现在其他地方(即在 UnmanagedMemoryStream 的 Read 方法中)当然我忘记了关于终结器有多危险的文章。最后,办公室里有人建议将某种输出放入每个非托管对象的终结器中,以跟踪它们的存在。不用说,其中一些“很早就”被摧毁了。
【解决方案2】:

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

【讨论】:

  • @ThomasEding - Yes it is。 C# 使用析构函数语法,但它实际上创建了一个finalizerAgain.
  • @JDB:语言构造被称为析构函数。我不喜欢这个名字,但这就是它的名字。声明析构函数的行为会导致编译器生成一个终结器方法,该方法包含一些包装代码以及析构函数主体中出现的任何内容。
【解决方案3】:

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

【讨论】:

  • 嗯,其实它叫做析构函数
  • 现在我很困惑。它是终结器还是析构器?
  • C# 规范实际上将其称为析构函数。有些人认为这是一个错误。 stackoverflow.com/questions/1872700/…
  • @ThomasEding - Yes it is。 C# 使用析构函数语法,但它实际上是在创建 finalizer
  • 我喜欢这里的 cmets,真正的 panto :)
【解决方案4】:

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

当您的类的用户忘记调用 Dispose 时,这是一种备用解决方案,以确保(最终)您的资源被释放,但您无法保证何时调用析构函数。

在这个Stack Overflow question 中,接受的答案正确地显示了如何实现 dispose 模式。仅当您的类包含垃圾收集器无法自行清理的任何未处理资源时才需要这样做。

一个好的做法是在不让类的用户手动处置对象以立即释放资源的情况下不实现终结器。

【讨论】:

  • 实际上它在 C# 中不被称为析构函数是有充分理由的。
  • 其实就是。感谢您对我投反对票,因为您错了。有关此特定问题,请参阅 MSDN 库:msdn.microsoft.com/en-us/library/66x5fx1b.aspx
  • @TomTom 它的正式名称是析构函数
  • 它实际上不是一个后备方法,它只是让 GC 管理您的对象何时释放非托管资源,实现 IDisposable 允许您自己管理。
【解决方案5】:

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

【讨论】:

  • 我不仅在 WPF 应用程序中做同样的事情。如果在您知道不再使用的类上从未调用析构函数,那么您必须进一步调查为什么该对象不被垃圾收集。如果断点命中析构函数,那么一切都应该没问题,否则就会出现内存泄漏。
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    析构函数提供了一种释放封装在类中的非托管资源的隐式方法,当 GC 处理它时会调用它们,并隐式调用基类的 Finalize 方法。如果您正在使用大量非托管资源,最好提供一种通过 IDisposable 接口释放这些资源的显式方法。请参阅 C# 编程指南:http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

    【讨论】:

      猜你喜欢
      • 2013-10-17
      • 1970-01-01
      • 2010-09-22
      • 2014-07-09
      • 2021-05-03
      • 2011-09-11
      • 2020-07-16
      • 2021-07-26
      相关资源
      最近更新 更多