Подтвердить что ты не робот

Должен ли быть выпущен * каждый * объект взаимодействия Excel с помощью Marshal.ReleaseComObject?

Edit

См. также Как правильно очистить объекты взаимодействия Excel?. Недавно я столкнулся с этим вопросом, и он дал много информации о том, как правильно утилизировать COM-объекты. Определенно проверяйте за первым (отмеченным) ответом, потому что другие ответы выходят за рамки простых "не использовать две точки" и "используйте ReleaseComObject для каждого com-объекта".

Я снова рассмотрел этот вопрос, потому что понял, что, несмотря на тщательную регистрацию и удаление всех моих COM-объектов, мои экземпляры Excel по-прежнему не были правильно настроены. Оказывается, существуют способы создания COM-объектов, которые совершенно неочевидны (т.е. Вы можете пропускать объекты COM, даже если вы никогда не используете две точки). Кроме того, даже если вы тщательно, если ваш проект превышает определенный размер, вероятность пропустить COM-объект приближается к 100%. И может быть очень сложно найти тот, который вы пропустили, когда это произойдет. Ответы на связанный с этим вопрос предоставляют некоторые другие методы для уверенности в том, что экземпляр Excel определенно закрыт. Между тем, я сделал небольшое (но значительное) обновление для моего ComObjectManager (ниже), чтобы отразить то, что я узнал из связанного выше вопроса.

Оригинальный вопрос

Я видел несколько примеров, где Marshal.ReleaseComObject() используется с объектами Excel Interop (т.е. объекты из пространства имен Microsoft.Office.Interop.Excel), но я видел, что он используется в разной степени.

Мне интересно, могу ли я уйти от чего-то вроде этого:

var application = new ApplicationClass();
try
{
    // do work with application, workbooks, worksheets, cells, etc.
}
finally
{
    Marashal.ReleaseComObject(application)
}

Или, если мне нужно освободить каждый созданный объект, как в этом методе:

public void CreateExcelWorkbookWithSingleSheet()
{
    var application = new ApplicationClass();
    var workbook = application.Workbooks.Add(_missing);
    var worksheets = workbook.Worksheets;
    for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
    {
        var worksheet = (WorksheetClass)worksheets[worksheetIndex];
        worksheet.Delete();
        Marshal.ReleaseComObject(worksheet);
    }
    workbook.SaveAs(
        WorkbookPath, _missing, _missing, _missing, _missing, _missing,
        XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
    workbook.Close(true, _missing, _missing);
    application.Quit();
    Marshal.ReleaseComObject(worksheets);
    Marshal.ReleaseComObject(workbook);
    Marshal.ReleaseComObject(application);
}

Что побудило меня задать этот вопрос, так это то, что я являюсь преданным LINQ, я действительно хочу сделать что-то вроде этого:

var worksheetNames = worksheets.Cast<Worksheet>().Select(ws => ws.Name);

... но я обеспокоен тем, что у меня закончится утечка памяти или призрачные процессы, если я не выпущу каждый рабочий лист (ws).

Любое понимание этого было бы оценено.

Обновление

На основе ответов до сих пор звучит так, будто мне действительно нужно выпустить каждый созданный объект com. Я воспользовался возможностью, чтобы создать класс ComObjectManager, чтобы немного облегчить эту головную боль. Вы должны помнить об использовании метода Get() каждый раз, когда вы создаете новый объект com, но если вы это сделаете, он позаботится обо всем остальном для вас. Пожалуйста, дайте мне знать, если у вас возникнут какие-либо проблемы (или отредактируйте и оставьте комментарий, если сможете). Здесь код:

public class ComObjectManager : IDisposable
{
    private Stack<object> _comObjects = new Stack<object>();

    public TComObject Get<TComObject>(Func<TComObject> getter)
    {
        var comObject = getter();
        _comObjects.Push(comObject);
        return comObject;
    }

    public void Dispose()
    {
        // these two lines of code will dispose of any unreferenced COM objects
        GC.Collect();
        GC.WaitForPendingFinalizers();

        while (_comObjects.Count > 0)
            Marshal.ReleaseComObject(_comObjects.Pop());
    }
}

Вот пример использования:

public void CreateExcelWorkbookWithSingleSheet()
{
    using (var com = new ComObjectManager())
    {
        var application = com.Get<ApplicationClass>(() => new ApplicationClass());
        var workbook = com.Get<Workbook>(() => application.Workbooks.Add(_missing));
        var worksheets = com.Get<Sheets>(() => workbook.Worksheets);
        for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
        {
            var worksheet = com.Get<WorksheetClass>(() => (WorksheetClass)worksheets[worksheetIndex]);
            worksheet.Delete();
        }
        workbook.SaveAs(
            WorkbookPath, _missing, _missing, _missing, _missing, _missing,
            XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
        workbook.Close(true, _missing, _missing);
        application.Quit();
    }
}
4b9b3361

Ответ 1

Я считаю, что вам нужно будет вызвать ReleaseComObject для каждого COM-объекта. Поскольку они не собираются с мусором, иерархия parent-child действительно не входит в уравнение: даже если вы освобождаете родительский объект, он не уменьшает счетчик ссылок на любые дочерние объекты.

Ответ 2

Вы должны вызвать Marshal.ReleaseComObject для каждого COM-объекта, который вы используете в своем коде, а не только для основного объекта приложения.