在我的析构函数中释放Excel对象

我正在使用Microsoft.Interropt.Excel DLL写一个Excel类。 我完成了所有的function,但我在我的析构函数中有一个错误。 我想保存所有更改到我的文件,我想释放所有来源。 我想把它们全部放在我的析构函数中。 但是在我的析构函数中,Excel.ApplicationClass,Workbook和Worksheet对象被一个Exception填充,这个“Exception对象具有已经从其底层RCW中分离出来的COM对象不能被使用”的消息。 所以我不能保存任何东西,什么都不closures,因为我们不能访问工作簿或工作表对象。

我无法访问析构函数中的类私有成员?

.NET对析构函数最接近的是.NET所谓的终结器。 主要区别在于析构函数通常具有确定性的终止(例如,当对象的引用计数变为零时),而在终止对象不再被引用之后的未确定时间调用.NET终结器。 这由.NET垃圾收集器使用根跟踪过程来处理,而不是使用简单的引用计数。

其中最好的文章之一是垃圾收集:Microsoft .NET Framework中的自动内存pipe理 。 有关详细信息,请参阅MSDN中的Finalize方法和析构函数。

我无法访问析构函数中的类私有成员?

不,你不能安全地做。

在你的情况下发生的事情是,当你的对象不再被根直接或间接引用时,你的对象引用的COM对象 – 也就是私有字段引用的对象 – 也不被引用无论是。 (被对象的字段引用并不会使这些COM对象保持活动状态,因为你的对象不再被引用或者被追踪到,所以COM对象也不会跟踪根。)所以你的对象和它引用的所有COM对象都可以同时被垃圾回收。 一段时间后,垃圾收集器将清理你的对象,并调用它的终结器,因为它也将处理COM对象,每个对象实际上是一个运行时可调用包装器(RCW) 。

麻烦的是,不仅是这些对象被垃圾收集的时间是不确定的,而且最终调用者被调用的顺序也是不确定的。 在这种情况下,一个运行时可调用包装器也有一个终结器,它本身调用Marshal.ReleaseComObject ,其结果是在该篱笆的COM侧递减引用计数,以便该COM对象可以被释放。 但是由于终结器被调用的顺序是不确定的,所以你的对象引用的COM对象的终结器很可能会在对象的终结器之前触发。 所以你的终结器中的代码有时候可能会工作,但是大多数情况下,你的对象引用的一个或多个运行时可调用包装器已经调用了终结器,而底层COM对象将会在你的终结器之前被释放得到执行它的代码。

简而言之,你应该避免使用终结器,而且你也不应该从终结器中访问引用types,因为这些引用types可能已经被终结了。

为了纠正你的情况,我会考虑两种不同的可能性:

  1. 在创build它们的相同方法中处理COM对象。 我在这里和这里有几个讨论。

  2. 通过使用IDisposable接口来启用对象的确定性处置,而不是依赖于非确定性终结器。

有关如何实现IDisposable模式的文章,请参阅:

  • 实施处理方法(MSDN)
  • Dispose,Finalization和资源pipe理(Joe Duffy)
  • 处理和确定方法(Rohit Gupta)

迈克

我不确定我是否在编写错误的代码 – 试图在这里的例子。 我发现,当我利用IDisposable模式的一切工作,除非我需要处理工作簿事件。

在我的情况下,用户可以在closures应用程序之前closures工作簿。 我已经声明Excel对象WithEvents并编写了WorkbookBeforeClose处理程序来满足要求。

在这种情况下,当我closures我的应用程序时(我已经closures了Excel),我得到了“与其基础RCW分离的COM对象无法使用”错误。 调用Dispose(False)时,Finalize中发生错误。

如果我离开使用事件声明Excel对象,但不编码任何处理程序,问题就消失了。

在我的Dispose中,我必须吞下Workbooks.Close和Quit的错误,因为它们是导致错误的语句。

不,你不应该访问析构函数中的任何托pipe对象:这包括COM RCWs。

相反,实现标准的IDisposable模式,并释放您的COM对象在Dispose(布尔)方法,就像你一次性pipe理对象。