为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
子衿沉夜 2019-05-21 16:23:24
我在C#(ApplicationClass)中使用Excel互操作,并在我的finally子句中放置了以下代码:while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }excelSheet = null;GC.Collect();GC.WaitForPendingFinalizers();虽然这种工作,Excel.exe即使我关闭Excel后,该过程仍然在后台。只有在我的应用程序手动关闭后才会发布。我做错了什么,或者是否有其他方法可以确保互操作对象得到妥善处理?
查看完整描述

4 回答

?
MMMHUHU

TA贡献1834条经验 获得超8个赞

Excel不会退出,因为您的应用程序仍然保持对COM对象的引用。

我猜你是在调用COM对象的至少一个成员而不将其分配给变量。

对我来说,我直接使用excelApp.Worksheets对象而不将其分配给变量:

Worksheet sheet = excelApp.Worksheets.Open(...);...Marshal.ReleaseComObject(sheet);

我不知道内部C#为Worksheets COM对象创建了一个包装器,它没有被我的代码释放(因为我不知道它),这也是为什么没有卸载Excel的原因。

我在这个页面找到了我的问题的解决方案,它也有一个很好的规则,用于在C#中使用COM对象:

切勿对COM对象使用两个点。


因此,有了这些知识,正确的做法是:

Worksheets sheets = excelApp.Worksheets; // <-- The important partWorksheet sheet = sheets.Open(...);...
Marshal.ReleaseComObject(sheets);Marshal.ReleaseComObject(sheet);

POST MORTEM更新:

我希望每个读者都能仔细阅读Hans Passant的这个答案,因为它解释了我和许多其他开发人员陷入困境的陷阱。当我几年前写这个答案时,我不知道调试器对垃圾收集器的影响,并得出了错误的结论。为了历史,我保持我的答案不变,但请阅读此链接,不要采用“两点”的方式:了解.NET中的垃圾收集使用IDisposable清理Excel互操作对象


查看完整回答
反对 回复 2019-05-21
?
慕仙森

TA贡献1827条经验 获得超8个赞

您实际上可以干净地释放Excel应用程序对象,但您必须小心。


维护一个命名引用的建议绝对是你访问的每个COM对象,然后通过它明确地发布它Marshal.FinalReleaseComObject()在理论上是正确的,但不幸的是,在实践中很难管理。如果有人在任何地方滑动并使用“两个点”,或者通过for each循环或任何其他类似命令迭代单元格,那么你将拥有未引用的COM对象并冒着挂起的风险。在这种情况下,无法在代码中找到原因; 你必须通过眼睛审查你的所有代码,并希望找到原因,这个任务对于大型项目来说几乎是不可能的。


好消息是,您实际上不必维护对您使用的每个COM对象的命名变量引用。相反,调用GC.Collect()然后GC.WaitForPendingFinalizers()释放您没有引用的所有(通常是次要的)对象,然后显式释放您持有命名变量引用的对象。


您还应该按重要性的相反顺序发布命名引用:首先是范围对象,然后是工作表,工作簿,最后是Excel应用程序对象。


例如,假设您有一个名为Range的对象变量xlRng,一个名为Worksheet的变量xlSheet,一个名为的Workbook变量xlBook和一个名为的Excel Application变量xlApp,那么您的清理代码可能如下所示:


// Cleanup

GC.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#时的原理完全相同。


查看完整回答
反对 回复 2019-05-21
  • 4 回答
  • 0 关注
  • 726 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信