ОБНОВЛЕНИЕ (ноябрь 2016 г.)
Я только что прочитал убедительный аргумент Ханса Пассанта о том, что использование GC.Collect
на самом деле правильный путь. Я больше не работаю с Office (слава богу), но если бы я это сделал, я бы, вероятно, хотел бы попробовать еще раз - это, безусловно, упростило бы большую часть (тысячи строк) кода, который я написал, пытаясь сделать все правильно "путь (как я тогда это видел).
Я оставлю свой оригинальный ответ для потомков ...
Как говорит Майк в своем ответе, есть простой и трудный способ справиться с этим. Майк предлагает использовать простой способ, потому что ... это проще. Лично я не верю, что это достаточно веская причина, и я не верю, что это правильный путь. Мне нравится "выключи и включи".
У меня есть многолетний опыт разработки приложений автоматизации Office для .NET, и эти проблемы взаимодействия COM мучили меня в течение первых нескольких недель и месяцев, когда я впервые столкнулся с этой проблемой, не в последнюю очередь потому, что Microsoft очень скромно признает, что есть во-первых, проблема, и в то время в интернете трудно было найти хороший совет.
У меня есть способ работы, который я сейчас использую практически, не задумываясь об этом, и вот уже много лет, как у меня была проблема. По-прежнему важно быть живым со всеми скрытыми объектами, которые вы, возможно, создаете - и да, если вы пропустите один из них, у вас может быть утечка, которая станет очевидной гораздо позже. Но это не хуже, чем было в старые дурные времена malloc
/ free
.
Я действительно думаю, что нужно кое-что сказать для того, чтобы убирать за собой по ходу дела, а не в конце. Если вы только запускаете Excel, чтобы заполнить несколько ячеек, то, возможно, это не имеет значения - но если вы собираетесь делать тяжелую работу, то это другой вопрос.
В любом случае, я использую технику, которая заключается в использовании класса-обертки, который реализует IDisposable
и который в своем методе Dispose
вызывает ReleaseComObject
. Таким образом, я могу использовать операторы using
, чтобы гарантировать удаление объекта (и освобождение объекта COM), как только я закончу с ним.
Важно отметить, что он будет удален / освобожден, даже если моя функция вернется рано, или будет исключение и т. Д. Кроме того, он только будет удален / освобожден, если он был фактически создан в первом место - назовите меня педантом, но предлагаемый код, который пытается выпустить объекты, которые, возможно, на самом деле не были созданы, выглядит для меня как неаккуратный код. У меня есть подобное возражение против использования FinalReleaseComObject - вы должны знать, сколько раз вы вызывали создание ссылки COM, и поэтому должны иметь возможность выпускать ее такое же количество раз.
Типичный фрагмент моего кода может выглядеть так (или будет, если бы я использовал C # v2 и мог бы использовать обобщенные значения: -)):
using (ComWrapper<Excel.Application> application = new ComWrapper<Excel.Application>(new Excel.Application()))
{
try
{
using (ComWrapper<Excel.Workbooks> workbooks = new ComWrapper<Excel.Workbooks>(application.ComObject.Workbooks))
{
using (ComWrapper<Excel.Workbook> workbook = new ComWrapper<Excel.Workbook>(workbooks.ComObject.Open(...)))
{
using (ComWrapper<Excel.Worksheet> worksheet = new ComWrapper<Excel.Worksheet>(workbook.ComObject.ActiveSheet))
{
FillTheWorksheet(worksheet);
}
// Close the workbook here (see edit 2 below)
}
}
}
finally
{
application.ComObject.Quit();
}
}
Теперь я не собираюсь притворяться, что это не многословно, и отступ, вызванный созданием объекта , может выйти из-под контроля, если вы не разделите вещи на более мелкие методы. Этот пример - худший случай, так как все, что мы делаем, - это создание объектов. Обычно между скобками происходит гораздо больше, а накладные расходы намного меньше.
Обратите внимание, что в соответствии с приведенным выше примером я всегда передавал «обернутые» объекты между методами, а не голым COM-объектом, и вызывающий объект должен был бы распорядиться им (обычно с помощью оператора using
) , Точно так же я всегда возвращал бы завернутый объект, а не голый, и опять же вызывающая сторона должна была бы освободить его. Вы можете использовать другой протокол, но важно иметь четкие правила, как это было, когда мы привыкли делать свое собственное управление памятью.
Класс ComWrapper<T>
, используемый здесь, надеюсь, не требует особых пояснений. Он просто сохраняет ссылку на обернутый COM-объект и освобождает его явно (используя ReleaseComObject
) в своем методе Dispose
. Метод ComObject
просто возвращает типизированную ссылку на обернутый COM-объект.
Надеюсь, это поможет!
EDIT : я только сейчас перешел по ссылке на ответ Майка на другой вопрос , и я вижу, что в другом ответе на этот вопрос есть ссылка на класс-оболочку так же, как я предлагаю выше.
Кроме того, что касается ответа Майка на этот другой вопрос, я должен сказать, что меня почти соблазнил аргумент «просто используйте GC.Collect
». Тем не менее, я был в основном привлечен к этому на ложной предпосылке; На первый взгляд все выглядело так, как будто бы вообще не нужно беспокоиться о ссылках на COM. Однако, как говорит Майк, вам все равно нужно явно освобождать COM-объекты, связанные со всеми вашими переменными в области видимости - и поэтому все, что вы сделали, это уменьшили, а не удалили необходимость в управлении COM-объектами. Лично я бы предпочел пройти всю свинью.
Я также отмечаю тенденцию во многих ответах писать код, где все освобождается в конце метода, в большом блоке ReleaseComObject
вызовов. Это все очень хорошо, если все работает как запланировано, но я призываю любого, кто пишет серьезный код, подумать, что произойдет, если возникнет исключение или если у метода будет несколько точек выхода (код не будет выполнен, и, следовательно, объекты COM). не будет выпущен). Вот почему я предпочитаю использовать «обертки» и using
s. Это многословно, но делает пуленепробиваемый код.
EDIT2 : я обновил код выше, чтобы указать, где должна быть закрыта рабочая книга с сохранением изменений или без них. Вот код для сохранения изменений:
object saveChanges = Excel.XlSaveAction.xlSaveChanges;
workbook.ComObject.Close(saveChanges, Type.Missing, Type.Missing);
... и не сохранить изменения, просто измените xlSaveChanges
на xlDoNotSaveChanges
.