如何正确清理Excel互操作对象?

我在C#(ApplicationClass)中使用Excel互操作,并在我的finally子句中放置了以下代码:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }excelSheet = null;GC.Collect();GC.WaitForPendingFinalizers();

虽然这种工作,Excel.exe即使我关闭Excel后,该过程仍然在后台。只有在我的应用程序手动关闭后才会发布。

我做错了什么,或者是否有其他方法可以确保互操作对象得到妥善处理?


子衿沉夜
浏览 727回答 4
4回答

MMMHUHU

Excel不会退出,因为您的应用程序仍然保持对COM对象的引用。我猜你是在调用COM对象的至少一个成员而不将其分配给变量。对我来说,我直接使用excelApp.Worksheets对象而不将其分配给变量:Worksheet&nbsp;sheet&nbsp;=&nbsp;excelApp.Worksheets.Open(...);...Marshal.ReleaseComObject(sheet);我不知道内部C#为Worksheets&nbsp;COM对象创建了一个包装器,它没有被我的代码释放(因为我不知道它),这也是为什么没有卸载Excel的原因。我在这个页面找到了我的问题的解决方案,它也有一个很好的规则,用于在C#中使用COM对象:切勿对COM对象使用两个点。因此,有了这些知识,正确的做法是:Worksheets&nbsp;sheets&nbsp;=&nbsp;excelApp.Worksheets;&nbsp;//&nbsp;<--&nbsp;The&nbsp;important&nbsp;partWorksheet&nbsp;sheet&nbsp;=&nbsp;sheets.Open(...);... Marshal.ReleaseComObject(sheets);Marshal.ReleaseComObject(sheet);POST MORTEM更新:我希望每个读者都能仔细阅读Hans Passant的这个答案,因为它解释了我和许多其他开发人员陷入困境的陷阱。当我几年前写这个答案时,我不知道调试器对垃圾收集器的影响,并得出了错误的结论。为了历史,我保持我的答案不变,但请阅读此链接,不要采用“两点”的方式:了解.NET中的垃圾收集和使用IDisposable清理Excel互操作对象

慕仙森

您实际上可以干净地释放Excel应用程序对象,但您必须小心。维护一个命名引用的建议绝对是你访问的每个COM对象,然后通过它明确地发布它Marshal.FinalReleaseComObject()在理论上是正确的,但不幸的是,在实践中很难管理。如果有人在任何地方滑动并使用“两个点”,或者通过for each循环或任何其他类似命令迭代单元格,那么你将拥有未引用的COM对象并冒着挂起的风险。在这种情况下,无法在代码中找到原因; 你必须通过眼睛审查你的所有代码,并希望找到原因,这个任务对于大型项目来说几乎是不可能的。好消息是,您实际上不必维护对您使用的每个COM对象的命名变量引用。相反,调用GC.Collect()然后GC.WaitForPendingFinalizers()释放您没有引用的所有(通常是次要的)对象,然后显式释放您持有命名变量引用的对象。您还应该按重要性的相反顺序发布命名引用:首先是范围对象,然后是工作表,工作簿,最后是Excel应用程序对象。例如,假设您有一个名为Range的对象变量xlRng,一个名为Worksheet的变量xlSheet,一个名为的Workbook变量xlBook和一个名为的Excel Application变量xlApp,那么您的清理代码可能如下所示:// CleanupGC.Collect();GC.WaitForPendingFinalizers();Marshal.FinalReleaseComObject(xlRng);Marshal.FinalReleaseComObject(xlSheet);xlBook.Close(Type.Missing, Type.Missing, Type.Missing);Marshal.FinalReleaseComObject(xlBook);xlApp.Quit();Marshal.FinalReleaseComObject(xlApp);在大多数代码示例中,您将看到从.NET清理COM对象,GC.Collect()并且GC.WaitForPendingFinalizers()调用TWICE,如下所示:GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();GC.WaitForPendingFinalizers();但是,除非您使用的是Visual Studio Tools for Office(VSTO),否则不需要这样做,因为它使用终结器来导致在终结队列中提升整个对象图。在下一次垃圾收集之前,这些对象不会被释放。但是,如果你不使用VSTO,你应该能够调用GC.Collect()和GC.WaitForPendingFinalizers()一次。我知道明确地说GC.Collect()是一个禁忌(当然这两次听起来很痛苦),但说实话,没有办法解决它。通过正常操作,您将生成隐藏的对象,您不会引用该对象,因此您无法通过除调用之外的任何其他方式释放GC.Collect()。这是一个复杂的话题,但这就是它的全部内容。为清理过程建立此模板后,您可以正常编码,无需包装等。:-)我在这里有一个教程:使用VB.Net / COM Interop自动化Office程序它是为VB.NET编写的,但不要因此而被推迟,其原理与使用C#时的原理完全相同。
打开App,查看更多内容
随时随地看视频慕课网APP