Создание повторно используемых стилей Excel для объектов взаимодействия C#
Я создаю пару электронных таблиц с использованием 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);
Стиль работает, как и ожидалось, когда я применяю его, но процесс Excel не завершается при выходе из приложения Excel. Если я закомментирую строку columnHeader.Font.Size = 12;
Процесс Excel правильно завершается. Я что-то пропустил?
Обновить
Я изменил пример приложения WinForms от Govert, чтобы отразить структуру моего класса, которая создает несколько электронных таблиц. Его приложение правильно вышло из процесса 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();
}
}
}
2 ответа
Вам никогда не нужно звонить Marshal.ReleaseComObject
в данном контексте. Среда выполнения прекрасно умеет отслеживать COM-объекты и освобождать их, когда на них больше нет ссылок. призвание Marshal.ReleaseComObject
это запутанный анти-паттерн, который, к сожалению, даже некоторые документы Microsoft ошибочно предполагают. Вам также не нужно устанавливать локальные переменные в null
- ссылки будут выходить за рамки при завершении метода.
Чтобы закрыть Excel, вам нужно позвонить excelApplication.Quit()
, затем убедитесь, что у вас нет живых ссылок на объекты COM COM Excel, а затем вызовите сборщик мусора для очистки. Вы должны вызывать сборщик мусора дважды - у вас могут быть случаи, когда ссылки образуют цикл, и первый вызов GC прервет цикл, но объекты COM могут быть должным образом освобождены только при втором вызове.
Вы также должны быть осторожны с этим видом кода в отладочных сборках. Ссылки в методе искусственно поддерживаются до конца метода, так что они все еще будут доступны в отладчике. Это означает, что ваши локальные COM-объекты Excel не могут быть очищены путем вызова 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;
}
Это обеспечит, чтобы поля уровня объекта, представленные в обновленном вопросе, удалили ссылки в Excel до запуска GC.
В моем тестировании с этой модификацией Excel снова выходит. Это согласуется с моим пониманием того, что среда выполнения.NET правильно отслеживает ссылки COM и что Marshal.ReleaseComObject
никогда не требуется для взаимодействия Excel.
Вероятно, зависшая ссылка на 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();