简体   繁体   English

为 C# 互操作对象创建可重用的 Excel 样式

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

I'm generating a couple of spreadsheets using C#, and I'm trying to create a reusable Style that can be applied to specific ranges in each spreadsheet.我正在使用 C# 生成几个电子表格,并且我正在尝试创建一个可重用的样式,该样式可以应用于每个电子表格中的特定范围。 The issue I'm having is that the Excel process is not exiting correctly when my method finishes executing.我遇到的问题是,当我的方法完成执行时,Excel 进程没有正确退出。 In fact, I've been able to narrow the problem area down to the point in the code where I'm creating the Excel Style .事实上,我已经能够将问题范围缩小到我创建 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);

The style works as expected when I apply it, but the Excel process doesn't quit when I quit my excelApplication.当我应用它时,该样式按预期工作,但是当我退出我的 excelApplication 时,Excel 进程并没有退出。 If I comment out the line columnHeader.Font.Size = 12;如果我注释掉columnHeader.Font.Size = 12; , the Excel process correctly exits. ,Excel 进程正确退出。 Am I missing something?我错过了什么吗?

Update更新
I modified Govert's sample WinForms application to reflect the structure of my class, which is creating multiple spreadsheets.我修改了 Govert 的示例 WinForms 应用程序以反映我创建多个电子表格的类的结构。 His application properly exited the Excel process, but my modified version does not:他的应用程序正确退出了 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();
        }
    }
}

Probably a hanging reference to the Font / columnHeader or styles, accessing properties of properties in Excel interop can cause the Excel process to hang;可能是对 Font/columnHeader 或样式的挂起引用,在 Excel 互操作中访问属性的属性会导致 Excel 进程挂起; try this尝试这个

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

You never need to call Marshal.ReleaseComObject in this context.在这种情况下,您永远不需要调用Marshal.ReleaseComObject The runtime is perfectly able to keep track of COM objects and release them when they are no longer referenced.运行时完全能够跟踪 COM 对象并在它们不再被引用时释放它们。 Calling Marshal.ReleaseComObject is a confusing anti-pattern that sadly even some Microsoft documentation mistakenly suggests.调用Marshal.ReleaseComObject是一种令人困惑的反模式,可悲的是,即使是一些 Microsoft 文档也错误地暗示了这一点。 You also don't have to set local variables to null - the references will be out of scope when the method completes.您也不必将局部变量设置为null - 当方法完成时,引用将超出范围。

To get Excel to close, you need to call excelApplication.Quit() , then make sure you have no live references to Excel COM objects, then call the garbage collector to clean up.要关闭 Excel,您需要调用excelApplication.Quit() ,然后确保您没有对 Excel COM 对象的实时引用,然后调用垃圾收集器进行清理。 You should call the garbage collector twice - you might have cases where the references form a cycle, and the first GC call will break the cycle, but the COM objects might only get properly released on the second call.您应该调用垃圾收集器两次 - 您可能会遇到引用形成循环的情况,并且第一次 GC 调用将中断循环,但 COM 对象可能只会在第二次调用时正确释放。

You also have to be careful with this kind of code in debug builds.您还必须小心在调试版本中使用此类代码。 References in a method are artificially kept alive until the end of the method so that they will still be accessible in the debugger.方法中的引用会人为地保持活动状态直到方法结束,以便在调试器中仍然可以访问它们。 This means your local Excel COM objects might not be cleaned up by calling the GC inside that method.这意味着您的本地 Excel COM 对象可能不会通过调用该方法内的 GC 来清理。 To avoid this issue, you might follow a pattern like this:为了避免这个问题,你可以遵循这样的模式:

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

I've now tested with a new WinForms application, to which I add a single button and this code behind:我现在已经用一个新的 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();
        }
    }
}

Everything works as expected, with the Excel process stopping during the GarbageCleanup() call.一切都按预期工作,Excel 进程在GarbageCleanup()调用期间停止。


Update更新

After the update to the question, I suggest the following modification:问题更新后,我建议进行以下修改:

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

This will ensure that the object-level fields that are introduced in the updated question have Excel their references removed before the GC runs.这将确保在更新的问题中引入的对象级字段在 GC 运行之前删除了 Excel 的引用。

In my testing, with this modification Excel quits again.在我的测试中,经过此修改,Excel 再次退出。 This is consistent with my understanding that the .NET runtime properly keeps track of COM references, and that Marshal.ReleaseComObject is never required for Excel interop.这与我的理解一致,即 .NET 运行时正确跟踪 COM 引用,并且 Excel 互操作从不需要Marshal.ReleaseComObject

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM