Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/image/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为C#互操作对象创建可重用的Excel样式_C#_Excel_Excel Interop - Fatal编程技术网

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

为C#互操作对象创建可重用的Excel样式,c#,excel,excel-interop,C#,Excel,Excel Interop,我正在使用C#生成两个电子表格,并且我正在尝试创建一个可重用的,可应用于每个电子表格中特定范围的电子表格。我遇到的问题是,当我的方法完成执行时,Excel进程没有正确退出。事实上,我已经能够将问题区域缩小到代码中创建Excel样式的地方 Workbooks books = excelApplication.Workbooks; _Workbook wBook = books.Add(""); _Worksheet wSheet = (_Worksheet)wBook.ActiveSheet;

我正在使用C#生成两个电子表格,并且我正在尝试创建一个可重用的,可应用于每个电子表格中特定范围的电子表格。我遇到的问题是,当我的方法完成执行时,Excel进程没有正确退出。事实上,我已经能够将问题区域缩小到代码中创建Excel
样式的地方

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);
当我应用样式时,它会按预期工作,但当我退出Excel应用程序时,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();
        }
    }
}

可能是对字体/列标题或样式的挂起引用,访问Excel interop中属性的属性会导致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再次退出。这与我的理解是一致的,即.NET运行时正确地跟踪COM引用,Excel互操作从不需要封送.ReleaseComObject

运气不好,进程仍然挂起。@Gunnerbanes刚刚注意到;您是否在excelApplication anywhere的实例上执行ReleaseComObject?(编辑我的代码以添加它)是的,我应该在我的代码中注意到这一点,但事实上我已经在稍后做了。Excel进程将按预期退出,除非我在Style.ok.上设置了任何属性。。请尝试更新的版本。这可能很难找到,我感觉到你的痛苦!这也没有什么不同。我没有将任何局部变量设置为null,但是如果我没有调用
Marhsal.ReleaseComObject
,垃圾收集器没有正确地处理COM对象。您是否正在检查调试生成?GC.Collect()代码的方法是否与您的问题中的方法相同?如果是这样,您可能需要再次检查。您的应用程序确实正确地停止了Excel进程。我进一步修改了表单类的结构,使之类似于我的表单类,并使Excel流程再次挂起。我已经更新了上面的问题。确定-通过更改,您设置了两个对象级字段,用于保存对COM对象的引用。在调用垃圾收集器之前,需要清除这些引用(通过将这些字段设置为null)。这与我提到的局部变量不同,局部变量不需要设置为null,因为它们在方法末尾超出了范围。如果向Excel COM对象引入表单级引用,请参阅我的更新答案,以获取所需的额外清理。