Достаточно часто при первом знакомстве работы с Excel в C# возникает следующая проблема — после прекращения работы с Excel и завершения работы приложения в фоне остается «висеть» процесс Microsoft Excel. Разберемся с этой проблемой подробнее.
В 99% случаев процесс Excel остается в фоне после завершения работы приложения по причине невнимательности в части освобождения ранее используемых COM-объектов. Чтобы не натыкаться каждый раз на эту проблему, стоит помнить простое правило работы с COM-объектами в C#:
Да, возможно, что такой подход приведет к росту количества кода приложения, но, при этом, вы гарантированно будете знать, видеть и освобождать ранее используемые COM-объекты.
Рассмотрим пример, когда это правило наглядно продемонстрировано.
Пример «висящего» процесса Excel
Пропустим процесс добавления необходимых ссылок на COM в приложение C# — вся информация по этому вопросу находится в этой статье. Пример следующий: есть книга Excel с одним листом на котором расположена таблица. Нам необходимо считать значения из этой таблицы и вывести их в консоль. Пример таблицы:

Прочитаем эту таблицу в нашей программе:
using Excel = Microsoft.Office.Interop.Excel; using System.Runtime.InteropServices; using Microsoft.Office.Interop.Excel; namespace ExcelLock { internal class Program { static void Main(string[] args) { Workbooks workbooks = null; Workbook workbook = null; Worksheet sheet = null; Excel.Range range = null; Excel.Application application = new Excel.Application(); try { workbooks = application.Workbooks;//получили доступ к коллекции рабочих книг workbook = workbooks.Open(Path.Combine(Directory.GetCurrentDirectory(), "Book.xlsx")); //файл находится рядом с exe-файлом sheet = workbook.ActiveSheet;//получили доступ к активному листу книги range = sheet.UsedRange;//получили доступ к диапазону занятых ячеек var data = range.Value;//прочитали весь диапазон в память //читаем строки таблицу, начиная со второй (пропускаем шапку таблицы) for (int i = 2; i <= range.Rows.Count; i++) { Console.WriteLine($"Товар {data[i, 1]} Цена за кг {data[i, 2]} Масса {data[i, 3]} Стоимость {data[i, 4]}"); } application.Quit(); } finally { Marshal.ReleaseComObject(range); Marshal.ReleaseComObject(sheet); Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(workbooks); Marshal.ReleaseComObject(application); } } } }
На первый взгляд, всё должно работать без проблем — все использованные COM-объекты освобождены в блоке finally
, приложение читает таблицу и выводит результаты, но при этом, файл остается занятым процессом Excel, который остается висеть в памяти. Ошибка кроется в цикле for
— именно там при забыли про правило использования COM. Смотрим внимательно на описание цикла:
for (int i = 2; i <= range.Rows.Count; i++)
Мы запросили у объекта range
еще один объект Rows
и уже у Rows
запросили количество занятых строк таблицы. В итоге, после завершения работы приложения COM-объект range.Rows
оказался не освобожден и процесс Excel «завис» в памяти.
Используем не более одной точки при работе с COM
Чтобы приложение работало корректно, а процесс Excel выгружался из памяти, перепишем наш пример следующим образом:
using Excel = Microsoft.Office.Interop.Excel; using System.Runtime.InteropServices; using Microsoft.Office.Interop.Excel; namespace ExcelLock { internal class Program { static void Main(string[] args) { Workbooks workbooks = null; Workbook workbook = null; Worksheet sheet = null; Excel.Range range = null; Excel.Application application = new Excel.Application(); Excel.Range rows = null; //диапазон занятых строк try { workbooks = application.Workbooks; workbook = workbooks.Open(Path.Combine(Directory.GetCurrentDirectory(), "Book.xlsx")); sheet = workbook.ActiveSheet; range = sheet.UsedRange; rows = range.Rows; //получили диапазон строк var data = range.Value; //здесь запрашиваем значение у объекта rows for (int i = 2; i <= rows.Count; i++) { Console.WriteLine($"Товар {data[i, 1]} Цена за кг {data[i, 2]} Масса {data[i, 3]} Стоимость {data[i, 4]}"); } application.Quit(); } finally { Marshal.ReleaseComObject(rows); //не забываем освободить объект Marshal.ReleaseComObject(range); Marshal.ReleaseComObject(sheet); Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(workbooks); Marshal.ReleaseComObject(application); } } } }
Здесь мы сохранили объект диапазона строк в переменной Rows
и освободили его после использования. В результате, процесс Excel завершается корректно и не остается висеть в памяти.
Итого
При работе с Excel в C# крайне важно следить за тем, какие COM-объекты вы запрашиваете и используете. Правило «одной точки» позволяет избежать ситуации, когда COM-объект не освобождается и процесс Excel остается висеть в памяти, блокирую файл.