簡體   English   中英

為 C# 互操作對象創建可重用的 Excel 樣式

[英]Create reusable Excel Styles for C# interop objects

我正在使用 C# 生成幾個電子表格,並且我正在嘗試創建一個可重用的樣式,該樣式可以應用於每個電子表格中的特定范圍。 我遇到的問題是,當我的方法完成執行時,Excel 進程沒有正確退出。 事實上,我已經能夠將問題范圍縮小到我創建 Excel Style的代碼點。

Workbooks books = excelApplication.Workbooks;
_Workbook wBook = books.Add("");
_Worksheet wSheet = (_Worksheet)wBook.ActiveSheet;

Styles styles = wBook.Styles;
Style columnHeader = styles.Add("ColumnHeader");
columnHeader.Font.Size = 12; // if I comment this out, excel quits correctly

Marshal.ReleaseComObject(wSheet);
Marshal.ReleaseComObject(wBook);
Marshal.ReleaseComObject(books);

當我應用它時,該樣式按預期工作,但是當我退出我的 excelApplication 時,Excel 進程並沒有退出。 如果我注釋掉columnHeader.Font.Size = 12; ,Excel 進程正確退出。 我錯過了什么嗎?

更新
我修改了 Govert 的示例 WinForms 應用程序以反映我創建多個電子表格的類的結構。 他的應用程序正確退出了 Excel 進程,但我的修改版本沒有:

using System;
using Excel = Microsoft.Office.Interop.Excel;
using System.Windows.Forms;

namespace ExcelCOMReferenceTesting
{
    public partial class Form1 : Form
    {
        private Excel.Application excelApplication;
        private Excel.Style columnHeader;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            DoMyExcelStuff();
            GarbageCleanup();
        }

        private void DoMyExcelStuff()
        {
            StartExcel();
            var wBook = GenerateWorksheet();

            excelApplication.Range["A1"].Value = "Name";
            excelApplication.Range["A1"].Style = columnHeader;
            wBook.SaveAs(@"c:\Test\tst" + DateTime.Now.ToString("mmss") + ".xlsx");

            // No need for Marshal.ReleaseComObject(...)
            // No need for ... = null
            StopExcel();
        }

        private void StartExcel()
        {
            excelApplication = new Excel.Application();
            excelApplication.Visible = false;
        }

        private void StopExcel()
        {
            excelApplication.UserControl = false;
            excelApplication.Quit();
        }

        private Excel._Workbook GenerateWorksheet()
        {
            Excel.Workbooks books = excelApplication.Workbooks;
            Excel.Workbook wBook = books.Add("");

            Excel.Styles styles = wBook.Styles;
            columnHeader = styles.Add("ColumnHeader");
            columnHeader.Font.Size = 12;
            columnHeader.Font.Bold = true;

            return wBook;
        }

        private void GarbageCleanup()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

可能是對 Font/columnHeader 或樣式的掛起引用,在 Excel 互操作中訪問屬性的屬性會導致 Excel 進程掛起; 嘗試這個

Workbooks books = excelApplication.Workbooks;
_Workbook wBook = books.Add("");
_Worksheet wSheet = (_Worksheet)wBook.ActiveSheet;

Styles styles = wBook.Styles;
Style columnHeader = styles.Add("ColumnHeader");
Font font = columnHeader.Font;
font.Size = 12;

Marshal.ReleaseComObject(font);
Marshal.ReleaseComObject(columnHeader);
Marshal.ReleaseComObject(styles);
Marshal.ReleaseComObject(wSheet);
Marshal.ReleaseComObject(wBook);
Marshal.ReleaseComObject(books);

font = null;
columnHeader = null;
styles = null;
wSheet = null;
wBook = null;
books = null;

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

在這種情況下,您永遠不需要調用Marshal.ReleaseComObject 運行時完全能夠跟蹤 COM 對象並在它們不再被引用時釋放它們。 調用Marshal.ReleaseComObject是一種令人困惑的反模式,可悲的是,即使是一些 Microsoft 文檔也錯誤地暗示了這一點。 您也不必將局部變量設置為null - 當方法完成時,引用將超出范圍。

要關閉 Excel,您需要調用excelApplication.Quit() ,然后確保您沒有對 Excel COM 對象的實時引用,然后調用垃圾收集器進行清理。 您應該調用垃圾收集器兩次 - 您可能會遇到引用形成循環的情況,並且第一次 GC 調用將中斷循環,但 COM 對象可能只會在第二次調用時正確釋放。

您還必須小心在調試版本中使用此類代碼。 方法中的引用會人為地保持活動狀態直到方法結束,以便在調試器中仍然可以訪問它們。 這意味着您的本地 Excel COM 對象可能不會通過調用該方法內的 GC 來清理。 為了避免這個問題,你可以遵循這樣的模式:

public void DoMyExcelStuffAndCleanup()
{
    DoMyExcelStuff();
    // Call GC twice to ensure that cleanup after cycles happens immediately
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

public void DoMyExcelStuff()
{
    Application excelApplication = ...
    // Here you access all those Excel objects ...
    // No need for Marshal.ReleaseComObject(...)
    // No need for ... = null
    excelApplication.Quit();
}

我現在已經用一個新的 WinForms 應用程序進行了測試,我向其中添加了一個按鈕和下面的代碼:

using System;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;

namespace WinFormsComCleanup
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            DoMyExcelStuff();
            GarbageCleanup();
        }

        private void DoMyExcelStuff()
        {
            Excel.Application excelApplication = new Excel.Application();
            Excel.Workbooks books = excelApplication.Workbooks;
            Excel.Workbook wBook = books.Add("");
            Excel.Worksheet wSheet = (Excel.Worksheet)wBook.ActiveSheet;

            Excel.Styles styles = wBook.Styles;
            Excel.Style columnHeader = styles.Add("ColumnHeader");
            columnHeader.Font.Size = 12;
            columnHeader.Font.Bold = true;
            excelApplication.Range["A1"].Value = "Name";
            excelApplication.Range["A1"].Style = columnHeader;
            wBook.SaveAs(@"c:\Temp\tst" + DateTime.Now.ToString("mmss") +".xlsx");

            // No need for Marshal.ReleaseComObject(...)
            // No need for ... = null
            excelApplication.Quit();
        }

        private void GarbageCleanup()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}

一切都按預期工作,Excel 進程在GarbageCleanup()調用期間停止。


更新

問題更新后,我建議進行以下修改:

    private void StopExcel()
    {

        excelApplication.UserControl = false;
        excelApplication.Quit();

        // Set the form-level variables to null, so that no live references to Excel remain
        columnHeader = null;
        excelApplication = null;
    }

這將確保在更新的問題中引入的對象級字段在 GC 運行之前刪除了 Excel 的引用。

在我的測試中,經過此修改,Excel 再次退出。 這與我的理解一致,即 .NET 運行時正確跟蹤 COM 引用,並且 Excel 互操作從不需要Marshal.ReleaseComObject

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM