使用 IDisposable 清理 Excel 互操作对象

Clean up Excel Interop Objects with IDisposable

提问人:ruedi 提问时间:8/5/2014 最后编辑:ruedi 更新时间:4/27/2023 访问量:19389

问:

在我的公司中,发布 Excel 互操作对象的常用方法是使用以下方式:IDisposable

Public Sub Dispose() Implements IDisposable.Dispose
    If Not bolDisposed Then
        Finalize()
        System.GC.SuppressFinalize(Me)
    End If
End Sub

Protected Overrides Sub Finalize()
    _xlApp = Nothing
    bolDisposed = True
    MyBase.Finalize()
End Sub

其中按以下方式在构造函数中创建:_xlApp

Try
    _xlApp = CType(GetObject(, "Excel.Application"), Excel.Application)
Catch e As Exception
    _xlApp = CType(CreateObject("Excel.Application"), Excel.Application) 
End Try

客户端使用 执行有关 excel 互操作对象的代码。using-statement

我们完全避免使用两点法则。现在我开始研究如何发布 (Excel) 互操作对象,我发现几乎所有关于它的讨论,例如如何正确清理 excel 互操作对象或释放 Excel 对象,它们都主要使用,它们都没有使用界面。Marshal.ReleaseComObject()IDisposable

我的问题是:使用 interace 发布 excel 互操作对象有什么缺点吗?如果是这样,这些缺点是什么。IDisposable

.NET Excel vb.net 互操作 com-interop

评论

0赞 Lasse V. Karlsen 8/5/2014
不过,更常见的做法是以相反的方式实现 Dispose/Finalize,让终结器调用 dispose 方法。

答:

146赞 Hans Passant 8/5/2014 #1

使用 IDisposable Interace 有什么缺点吗

当然,它绝对什么也做不了。使用使用或调用 Dispose() 从来都不是将变量设置为 Nothing 的合适方法。这就是您的代码所做的一切。

我们完全避免使用两点法则。

随意继续忽略它,这是无稽之谈,只会引起悲伤。博客作者的隐含断言是,这样做会迫使程序员使用变量来存储 xlApp.Workbooks 的值。因此,他以后有机会不忘记调用 releaseObject()。但是,还有更多语句生成不使用点的接口引用。类似于 Range(x,y) 之类的东西,那里有一个隐藏的 Range 对象引用,你永远不会看到。还必须存储它们只会产生令人难以置信的复杂代码。

仅仅忽略一个就足以完全无法完成工作。完全不可能调试。这是 C 程序员必须编写的代码。大型 C 程序经常失败,经常泄漏内存,它们的程序员花费大量时间寻找这些泄漏。当然不是 .NET 方式,它有一个垃圾回收器来自动执行此操作。它永远不会出错。

麻烦的是,它在处理工作时有点慢。非常有意为之。除了在这种代码中,没有人会注意到这一点。您可以看到垃圾回收器未运行,您仍然看到 Office 程序正在运行。当你写 xlapp 时,它并没有退出。Quit(),它仍然存在于任务管理器的“进程”选项卡中。他们想要的是,当他们这么说时,它就会退出。

这在 .NET 中是很有可能的,你当然可以强制 GC 完成工作:

GC.Collect()
GC.WaitForPendingFinalizers()

砰,每个 Excel 对象引用都会自动释放。无需自己存储这些对象引用并显式调用 Marshal.ReleaseComObject(),CLR 会为你执行此操作。而且它永远不会出错,它不使用或不需要“两点法则”,并且可以轻松找到那些隐藏的接口引用。


然而,重要的是你把这段代码放在哪里。大多数程序员把它放在错误的地方,用与使用这些 Excel 界面相同的方法。这很好,但在调试代码时不起作用,这是本答案中解释的一个怪癖。在博客作者的代码中执行此操作的正确方法是将代码移动到一个小的帮助程序方法中,我们将其称为 DoExcelThing()。喜欢这个:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    DoExcelThing()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    '' Excel.exe no longer running anymore at this point
End Sub

请记住,这实际上只是一个调试工件。程序员只是讨厌必须使用任务管理器来杀死僵尸Excel.exe实例。当他们停止调试器时僵尸化,阻止程序正常退出并收集垃圾。这是正常的。当您的程序因任何原因在生产中死亡时,也会发生这种情况。把你的精力放在它所属的地方,从你的代码中去除错误,这样你的程序就不会死掉。GC 不需要比这更多的帮助。

评论

25赞 Govert 8/22/2014
阿门!请继续与感染互联网的整个两点 ReleaseComObject 巫毒教废话作斗争(以及 +1 也指向调试怪癖)。
3赞 Mike 10/25/2014
感谢您澄清,没有两点的 Marshal.ReleaseComObject 样式对于执行 Excel(或我认为大多数其他 COM)互操作和仍然发布引用不是必需的。我听说过很多关于它的消息,我担心我将不得不重写我的大部分代码库。
3赞 rwong 7/16/2015
整个RCW设计已经是一匹死马了。未来将是一个自动生成的 C++/CLI 包装程序集(增强的互操作程序集),无论您将其保留还是留给 GC,它都将正确发布。错误设计是导致每个 C# 办公自动化不稳定的原因,导致每个人都从 COM 中逃跑,并最终导致每个人都从 Office 套件本身中逃跑。为时已晚,已经发生了。Dispose
4赞 Cornelius 7/23/2015
在我第一次阅读这篇文章时,我错过了关于我应该在哪里放置命令的部分。把它放在正确的位置后,它工作得很好。GC.Collect()
1赞 jpcguy89 10/7/2015
我对这一切有点困惑......我从来没有任何 Excel 的僵尸实例。当我告诉他我正在向 Excel 写信时,我的老板来给我看,他们不在那里,在任务管理器内签入流程和应用程序。我的问题是......从 VS2015 和 .NET 4.6 开始,GC 是否需要额外的帮助来清理 COM 对象这仍然是一个问题?
1赞 Antoine Dahan 8/22/2014 #2

如果您正在寻找一种更清洁的方式,您可以使用 Koogra。它没有太多额外的开销(只需在引用中包含两个 DLL),并且不必处理隐式垃圾回收。

下面的代码是从 excel 文件中读取 10 行 10 列所需的全部代码,没有留下任何 EXCEL32Excel.exe 进程。一个月前我遇到了这个问题,并写了关于如何做到这一点的说明。比直接处理 Excel Interop 要容易得多,也更干净。

Koogra.IWorkbook workbook = Koogra.WorkbookFactory.GetExcel2007Reader("MyExcelFile.xlsx");
Net.SourceForge.Koogra.IWorksheet worksheet = workbook.Worksheets.GetWorksheetByName("Sheet1");

//This will invididually print out to the Console the columns A-J (10 columns) for rows 1-10.
for (uint rowIndex = 1; rowIndex <= 10; rowIndex++)
{
    for (uint columnIndex = 1; columnIndex <= 10; columnIndex++)
    {
        Console.WriteLine(worksheet.Rows.GetRow(rowIndex).GetCell(columnIndex).GetFormattedValue());
    }
}