如果我检查它是否为空,为什么 Excel com 对象不会从内存中释放?

Why will Excel com object not release from memory if I check it for null?

我继承了一个现有的应用程序进行维护,但遇到了一个障碍:它不会在完成后从内存中释放 Excel 对象。我在这里提取了相关代码。这是一种使用 COM Interop 创建 Excel 工作簿的方法:

using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
...

private void CreateWorkbook(string workingDirectory, string fileName, object[,] cellValues)
{
    Excel.Application excel = null;
    Excel.Workbooks workbooks = null;
    Excel.Workbook workbook = null;
    Excel.Sheets worksheets = null;
    Excel.Worksheet worksheet = null;
    Excel.Range startRange = null;
    Excel.Range endRange = null;
    Excel.Range selectedRange = null;

    try
    {
        excel = new Excel.Application() { DisplayAlerts = false, Visible = false, ScreenUpdating = false, EnableAutoComplete = false };

        // Statement which stops the excel instance exiting:
        if(excel == null)
        {
            MessageBox.Show("Excel could not be started, please check your machine has office 2013 installed.");
            return;
        }

        workbooks = excel.Workbooks;
        workbook = workbooks.Add(Type.Missing);
        worksheets = workbook.Sheets;
        worksheet = (Excel.Worksheet)worksheets[1];            

        startCellRange = (Excel.Range)worksheet.Cells[1,1];
        endCellRange = (Excel.Range)worksheet.Cells[1 + cellValues.GetLength(0), 1 + cellValues.GetLength(1)]; 
        selectedCells = worksheet.Range[startCellRange, endCellRange];
        selectedCells.Value2 = cellValues;

        workbook.SaveAs(workingDirectory + fileName, Excel.XlFileFormat.xlOpenXMLWorkbook, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
        workbook.Close(true, Type.Missing, Type.Missing);
        excel.Quit();
    }
    catch(Exception ex)
    {
        try
        {
            // Catch methods should ideally never throw exceptions, so try close the workbook and quit the excel instance in a try/catch block:
            workbook.Close(false, Type.Missing, Type.Missing);
            excel.Quit();
        }
        catch { }
        MessageBox.Show("An error was encountered while trying to create the excel workbook.\n" + ex.Message + (ex.InnerException == null ? "" : "\n" + ex.InnerException.Message));
    }
    finally
    {
        // Now clean up the com interop stuff:
        // ===================================
        if (Marshal.IsComObject(rangeFont)) Marshal.ReleaseComObject(rangeFont);
        if (Marshal.IsComObject(rangeBorders)) Marshal.ReleaseComObject(rangeBorders);
        if (Marshal.IsComObject(selectedRange)) Marshal.ReleaseComObject(selectedRange);
        if (Marshal.IsComObject(endRange)) Marshal.ReleaseComObject(endRange);
        if (Marshal.IsComObject(startRange)) Marshal.ReleaseComObject(startRange);
        if (Marshal.IsComObject(worksheet)) Marshal.ReleaseComObject(worksheet);
        if (Marshal.IsComObject(worksheets)) Marshal.ReleaseComObject(worksheets);
        if (Marshal.IsComObject(workbook)) Marshal.ReleaseComObject(workbook);
        if (Marshal.IsComObject(workbooks)) Marshal.ReleaseComObject(workbooks);
        if (Marshal.IsComObject(excel)) Marshal.ReleaseComObject(excel);
        if (Marshal.IsComObject(excel)) Marshal.FinalReleaseComObject(excel);

        System.Threading.Thread.Sleep(100);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        System.Threading.Thread.Sleep(500);
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}  

如果我在 excel = new Excel.Application()... 之后注释掉 if 语句,该方法工作正常并处理 excel 对象。但是当包含 if (excel == null) 测试时,excel 对象不会从内存中释放。我一直在试图找出原因,但我很困惑。我知道运行时可以自动在 com 对象周围放置一个包装器(即当使用超过“2 个点”访问 com 对象时)。但是,仅仅检查 com 对象是否等于 null 似乎并不是运行时执行此操作的合乎逻辑的地方,如果确实是这样的话。

那么如何正确地检查 com 对象是否为 null?还有其他地方我想做检查,例如在尝试关闭工作簿并退出时在 catch 块内 Excel。但是,我现在担心检查 catch 块内的 excel object == null 是否会形成一个不可见的包装器,所以我使用了嵌套的 try..catch 块。但这也不对。

问题是,这是怎么回事,如果我的方法不正确,检查 Excel com 对象是否正确实例化的最佳实践是什么。

尝试将垃圾收集器调用移至包装器方法。这样您就可以确保在调用它时所有自动创建的引用都超出范围。

private void CreateWorkbookWithCleanup(...)
{
    CreateWorkbook(...);

    // Yes, we really want to call those two methods twice to make sure all
    // COM objects AND all RCWs are collected.
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

此外,根据我的经验,您不需要调用 Marshal.ReleaseComObject,如果您释放所有引用,垃圾收集器会释放所有内容。也不应该需要睡眠。 GC 调用无论如何都会阻塞。