为什么我的析构函数从未运行?

17

我有一个带有析构函数的空白Winform

public partial class Form1 : Form
{
    public Form1()
    {
        System.Diagnostics.Trace.WriteLine("Form1.Initialize " + this.GetHashCode().ToString());
        InitializeComponent();
    }
    ~Form1()
    {
        System.Diagnostics.Trace.WriteLine("Form1.Dispose " + this.GetHashCode().ToString());
    }
}

当表单被销毁时,我希望它能写入输出窗口:

(Form1打开)
Form1.Initialize 41149443
(Form1关闭)
Form1.Dispose 41149443

MSDN建议三种实现析构函数的方式:

然而,以上三种方式均无法将“Form1.Dispose 41149443”写入输出窗口。因此,我无法确定该表单是否已被销毁。有什么建议吗?

由于垃圾收集器的不确定性,我是否应该放弃实现这一点?

还有其他方法可以知道Form1是否已被垃圾回收?


为什么?你想做什么? - SLaks
你不能假设析构函数一定会被调用。(例如,如果你有足够的内存,从来不调用任何析构函数是完全有效的。) - Katriel
追踪日志没有被写入吗? - Emond
输出跟踪没有写入 "Form1.Dispose 41149443"。 - Jeson Martajaya
我不知道如何做。你有在应用程序终止前刷新输出跟踪的例子吗? - Jeson Martajaya
显示剩余2条评论
6个回答

12

在你列出的三种实现析构函数的方式中,只有一种涉及到析构函数,那就是~Destructor()

如果你实现了IDisposable并且释放了对象,那么Dispose中的代码将会运行,但没有理由认为你的析构函数也会运行。

我认为你在追求不可能的东西。析构函数会在垃圾回收器决定时运行。你无法控制这个过程。垃圾回收器完全有权利形成这样的看法:运行析构函数只是浪费时间,如果内存充足,它会这样做。

如果你需要可预测的释放、终结等操作,请使用IDisposable


IDisposable不会自动释放,您必须调用Dispose或使用using()。那么如何调用Dispose()? - slfan
2
@slfan 你在说什么?Dispose会在被调用时运行,通常是通过using。我在哪里说过Dispose会自动运行? - David Heffernan
2
是的,它也不会调用终结器!这就是为什么我不会称其为可预测的处理方式。这只是一种处理非托管资源的标准模式,任何具有终结器的类都应该实现IDisposable模式。但在我看来,这个答案并不是对上面问题的回复。 - slfan
1
@slfan,我甚至在答案中说了终结器不会运行。 - David Heffernan
嗯...我在想,C#中的“析构函数”是否有任何意义呢?通常它用于释放一些资源,关闭文件描述符(例如,在这里我试图关闭套接字),但由于没有关于析构函数被调用时间的提示,因此无法像通常那样使用它。 - Hi-Angel
显示剩余6条评论

4
除非您正在学习垃圾回收,否则析构函数不适合用于跟踪。您应该查看Dispose(在Form中可以重写)。这发生在释放非托管资源(例如窗口句柄)之后。
protected override void Dispose(bool disposing)
{
   System.Diagnostics.Trace.WriteLine(
      "Form1.Dispose " + (disposing ? "disposing " : "")
      + this.GetHashCode().ToString());
   base.Dispose (disposing);
}

如果您想查看表单/控件是否已被处理,请使用Control.IsDisposed 属性。

编辑:由于 GC.SuppressFinalize,如果显式调用Dispose(或由框架调用),则不会执行终结器方法(C#中的析构函数语法)。

有关更多信息,请参见实现Dispose方法

@agent-j,那不应该是 "Form1.Dispose " + (disposing ? "disposing " : "") 吗? - apacay
@slfan,我的观点是只有在必要时才会由终结器调用它。如果有人显式地处理了表单,则由于GC.SupressFinalize的存在,析构函数(finilize方法)可能永远不会被执行。有关更多信息,请参见Dispose模式。 - agent-j
@agent-j:你说得对,我没看到在Form类中实际上实现了带有bool参数的Dispose()方法。因此,你的Dispose方法将会从终结器或基本方法中被调用。但是,当你重写这个方法时,你应该始终调用base.Dispose(disposing)。 - slfan

2
在设计器中添加FormClosed事件。
例如:
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(Form1_FormClosed);

然后创建适当的函数来处理该事件。

1

是的,你应该放弃析构函数的想法,因为它们本质上是不确定性的。我不确定为什么你需要将表单disposed而不是只是关闭它,但在大多数情况下,简单地关闭它就足够了。

你可以使用IDisposable,但这取决于为什么需要对表单进行垃圾回收。如果需要重新使用它,只需创建另一个实例即可。


1

当没有引用存在且垃圾回收器恰好运行时,表单将被垃圾回收。您可以通过调用GC.Collect()来强制垃圾回收。

您不应在终结器(也称为析构函数)中引用任何其他对象,因为该对象可能已经被垃圾回收了。

如果您确实需要,可以使用内存分析工具来查找对象是否已被垃圾回收。

您还必须记住,终结器是从主线程以外的线程调用的。

编辑: 如果您的问题只是看不到跟踪输出,则可能需要打开自动刷新。

<configuration>
  <system.diagnostics>
    <trace autoflush="true" />
  </system.diagnostics>
</configuration>

编辑2: 您的表单可能存在外部引用,例如已注册的事件处理程序。我建议您在应用程序的管理区域中添加一个按钮,执行以下代码:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

这样垃圾回收器必须运行并应该销毁您的表单(在终结器中设置断点)。如果没有,您有一些引用指向需要销毁的对象。


好的,试试我的第二个猜测。也许你有一些关于这个表单的参考资料。 - slfan
我尝试了第二个选项,它起作用了。我认为你的答案比其他人更接近正确。 - gmail user

0
你可以尝试将你的表单包装在一个WeakReference对象中,然后检查它的IsAlive属性来确定它是否已被垃圾回收。但是,正如其他答案所述,最好还是让GC自行处理,相信它会做好工作!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接