Достаточно часто при первом знакомстве работы с 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 остается висеть в памяти, блокирую файл.