C# 析构函数在超出作用域后未被调用

5

当我超出范围后,我的析构函数存在问题(它会调用,但是需要对表单进行一些操作,例如更改单选按钮),也许是我的代码中有错误。请看:

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
            EventLogger.Print += delegate(string output)
            { if (!textBox1.IsDisposed) this.Invoke(new MethodInvoker(() => textBox1.AppendText(output + Environment.NewLine)), null); };
        }

        private void button1_Click(object sender, EventArgs e)
        {
            TestClass test = new TestClass();
        }
    }
    public static class EventLogger
    {
        public delegate void EventHandler(string output);
        public static event EventHandler Print;
        public static void AddLog(String TextEvent)
        {
            Print(TextEvent);
        }
    }
    public class TestClass
    {
        public TestClass()
        {
            EventLogger.AddLog("TestClass()");
        }
        ~TestClass()
        {
            EventLogger.AddLog("~TestClass()");
        }
    }

}

7
我建议您使用IDisposable模式而不是析构函数,特别是如果您没有操作系统级别的句柄。如果没有操作系统级别的句柄,您不应该使用析构函数。 - DarthVader
永远不要依赖析构函数。 - Anthony Pegram
另外一件事,你有问题吗?析构函数在垃圾回收时被调用,这是你无法控制的。你也不能强制GC进行清理。你的代码中根本不需要任何析构函数。 - DarthVader
4个回答

11

没错,因为这不是C++。Finalizer(而不是在C++中的析构函数)不能保证在对象离开其声明作用域后立即调用,它是在垃圾回收器决定清理时调用的。

我可以问一下为什么要使用finalizer吗?您是否维护对需要以尽可能确定性方式释放的非托管资源的引用(如果是,则请阅读IDisposable接口的相关知识)?C# finalizer的用例很少见,通常不实现它们。


当我不使用表单应用程序而是控制台时,一切都正常。 - user1112008
@user1112008,你的代码不需要析构函数。你为什么认为需要它呢? - DarthVader
我只需要自动化用户界面(UI)。每个类都作为文本包含在窗体上的列表视图中。在创建时,它从构造函数中添加到列表视图中,然后超出作用域(析构函数),它会从列表视图中删除。抱歉我的英语表达。 - user1112008
1
@user1112008 在C++中使用RAII是惯用语,但该模式不能直接转移到C#。在C#中最接近的是“using模式”,但您可能需要考虑完全不同的架构方式。由于析构函数,RAII实际上是一种特定于C ++的模式。 - Asik

4

C#不是C++。析构函数不会同步运行

你的代码本身没有错误,但看起来你可能需要实现“IDisposable模式”来为你的类提供一种方式,使调用者可以保证某些对象的销毁是同步执行的。


2
对于那些更熟悉 C++ 模式的人来说,查看 IDisposable 接口的文档会很有用:

http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

不幸的是,我刚试过了,它没有按照我想要的方式工作。我使用了一个自动对象来保存GUI光标的当前状态,切换到等待光标并在对象超出范围时恢复原始光标。IDisposable接口确实导致光标被恢复,但不是立即 - 所以等待光标显示时间太长。太遗憾了,因为这是一个非常有用的模式。
更新:C#的try/finally模式在一段时间后变得足够舒适:
public void do_something_time_consuming()
{
   ShowBusyCursor cursor = new ShowBusyCursor();

   try
   {
      ...
      return;
   }
   finally
   {
      cursor.done();
   }
}

0

终结器不是设计用来在作用域结束后立即调用的。它是在对象被垃圾回收时调用的,可能会在作用域结束后的毫秒到几天之后。

终结器并不适用于这种类型的代码。它仅适用于资源清理。

你不能强制它在作用域结束后立即执行某个操作,但可以通过 Close() 或类似方法告诉它在作用域结束前立即执行操作,以表示该对象已经完成使用。

例如:

private void button1_Click(object sender, EventArgs e)
{
    TestClass test = new TestClass();
    // do stuff
    test.Close();
}

注意:您可以像建议的那样实现IDisposable,但是这种用法并不完全符合IDisposable的预期用法,因此尽管它可以工作,但有点hackish。

1
是的,但将其放在析构函数中的美妙之处在于(在C++中),每当对象超出范围时都会调用它。这意味着每次从函数返回时都会调用析构函数。这样可以避免在函数中的每个返回语句之前调用test.Close() - orion elenzil

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