【问题标题】:Clean up Excel Interop Objects with IDisposable使用 IDisposable 清理 Excel 互操作对象
【发布时间】:2014-08-05 08:12:53
【问题描述】:

在我的公司,发布 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

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

我们完全避免使用two dot rule。现在我开始研究如何发布(Excel)互操作对象,我发现的几乎所有关于它的讨论,如How to properly clean up excel interop objectsRelease Excel Objects 大多使用Marshal.ReleaseComObject(),没有一个使用IDisposable 接口。

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

【问题讨论】:

  • 以相反的方式实现 Dispose/Finalize 更为常见,让终结器调用 dispose 方法。

标签: .net excel vb.net interop com-interop


【解决方案1】:

使用 IDisposable Interace 有什么缺点

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

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

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

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

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

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

GC.Collect()
GC.WaitForPendingFinalizers()

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


然而,重要的是确切地将此代码放在哪里。大多数程序员把它放在错误的地方,使用那些 Excel 接口的方法。这很好,但在调试代码时不起作用,this answer 中解释了一个怪癖。在博客作者的代码中,正确的做法是将代码移动到一个小辅助方法中,我们称之为 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 不需要更多帮助。

【讨论】:

  • 阿门!请继续与感染互联网的两个点、ReleaseComObject 巫毒废话作斗争(并且 +1 也指向调试怪癖)。
  • 感谢您澄清没有两个点的 Marshal.ReleaseComObject 样式对于执行 Excel(或者我认为大多数其他 COM)互操作并仍然发布引用不是必需的。我已经听到很多关于它的消息,我担心我不得不重写我的大部分代码库。 . .
  • 整个RCW design 已经是dead horse。未来将是一个自动生成的 C++/CLI 包装程序集(增强的互操作程序集),无论您是 Dispose 还是将其留给 GC,它都会正确释放。错误的设计是导致每个 C# 办公自动化不稳定的原因,导致每个人都逃离 COM,最终导致每个人都逃离 Office 套件本身。太晚了,已经发生了。
  • 在我第一次阅读这篇文章时,我错过了关于我应该在哪里放置GC.Collect() 命令的部分。把它放在正确的地方后,它工作得很好。
  • 我对这一切有点困惑……我从来没有任何 Excel 僵尸实例。我的老板进来告诉我,当我告诉他我正在写 Excel 时,他们不在那里,检查了进程和任务管理器内的应用程序。我的问题是......从 VS2015 和 .NET 4.6 开始,GC 是否需要额外的帮助来清理 COM 对象这仍然是一个问题?
【解决方案2】:

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

下面的代码是从没有留下 EXCEL32Excel.exe 进程的 excel 文件中读取 10 行 10 列所需的全部内容。一个月前我遇到了这个问题,wrote instructions on how to do it. 比直接处理 Excel 互操作更容易和更干净。

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());
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-15
    • 1970-01-01
    相关资源
    最近更新 更多