2013-05-18 2 views
3

У меня есть приложение WinForms, которое использует COM Interop для подключения к приложениям Microsoft Office. Я прочитал много материала о том, как правильно распоряжаться COM объектов и здесь типичный код из моего приложения, используя методы из собственной статьи от Microsoft (here):Освобождение COM-объектов в .NET изнутри перечислений и других ссылочных сценариев

Excel.Application excel = new Excel.Application(); 
Excel.Workbook book = excel.Workbooks.Add(); 
Excel.Range range = null; 

foreach (Excel.Worksheet sheet in book.Sheets) 
{ 
    range = sheet.Range["A2:Z2"]; 

    // Process [range] here. 
    range.MergeCells(); 

    System.Runtime.InteropServices.Marshal.ReleaseComObject(range); 
    range = null; 
} 

// Release explicitly declared objects in hierarchical order. 
System.Runtime.InteropServices.Marshal.ReleaseComObject(book); 
System.Runtime.InteropServices.Marshal.ReleaseComObject(excel); 

book = null; 
excel = null; 

// As taken from: 
// http://msdn.microsoft.com/en-us/library/aa679807(v=office.11).aspx. 
System.GC.Collect(); 
System.GC.WaitForPendingFinalizers(); 
System.GC.Collect(); 
System.GC.WaitForPendingFinalizers(); 

Вся обработка исключений была лишена, чтобы сделать код clearer для этого вопроса.

Что происходит с объектом [sheet] в цикле [foreach]? Предположительно, он не будет очищен, и мы не будем вмешиваться в него, пока он перечисляется. Одним из вариантов было бы использовать цикл индексирования, но это делает уродливый код, а некоторые конструкторы в библиотеках объектов Office даже не поддерживают индексацию.

Кроме того, цикл [foreach] ссылается на коллекцию [book.Sheets]. Оказывает ли это сиротство RCW?

Так два вопроса:

  • Каков наилучший подход к очистке при перечислении необходимо?
  • Что происходит с промежуточными объектами, такими как [Sheets] в [book.Sheets], так как они явно не объявлены или не очищены?

UPDATE:

Я был удивлен предложением Ханс Passant и счел необходимым обеспечить некоторый контекст.

Это клиент/серверное приложение, в котором клиент подключается к различным приложениям Office, включая Access, Excel, Outlook, PowerPoint и Word. Он имеет более 1500 классов (и растет), которые тестируют определенные задачи, выполняемые конечными пользователями, а также имитируют их в режиме обучения. Он используется для обучения и тестирования студентов в области владения офисом в академической среде. С несколькими разработчиками и множеством классов было сложно обеспечить соблюдение COM-совместимых методов кодирования. В конечном итоге я прибегал к созданию автоматических тестов с использованием комбинации анализа и анализа исходного кода, чтобы обеспечить целостность этих классов на этапе предварительного просмотра кода.

Пожелает предложению Ганса попробовать вернуться.

+1

Удалить все вызовы ReleaseComObject(). Создайте версию версии своей программы и запустите ее без отладчика. И используйте диспетчер задач, чтобы убедиться, что он работает отлично, без помощи. –

+0

Там никогда не бывает недостатка в мнениях вудов и необоснованных претензий, когда дело доходит до управления памятью COM. Недостаточно котировок вокруг «официальных», читайте, что команда VS [думает о практике] (http://blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered- dangerous.aspx). –

+0

@ ХансПасант: Спасибо. Я этого не думал и никогда не слышал. Мое приложение подключается к 7 различным приложениям Office (на стороне клиента, а не к серверному приложению) и по существу построено вокруг них (но не как надстройка, хотя). Поэтому, учитывая тяжелое взаимодействие, я, естественно, пользовался большой осторожностью. К счастью, я использовал принудительные методы для Interop, поэтому попытка, которую вы предлагаете, будет очень простой, несмотря на 1500+ классов, которые взаимодействуют с приложениями Office. Вернется обратно. –

ответ

5

Enumerating

Ваша переменная sheet петля, на самом деле, не будет выпущен. При написании кода interop для excel вы должны постоянно следить за своими RCW. Вместо использования значений foreach я обычно использую for, так как это заставляет меня осознавать всякий раз, когда я схватил ссылку, явно объявив эту переменную. Если вы должны перечислить, то в конце цикла (перед тем, как выйти из цикла) это сделать:

if (Marshal.IsComObject(sheet)) { 
    Marshal.ReleaseComObject(sheet); 
} 

И, будьте осторожны continue и break заявления, которые оставляют петлю, прежде чем вы выпустили вашу ссылку.

полупродукты

Это зависит от того, или нет промежуточного фактически COM объект (book.Sheets есть), но если после этого вам нужно сначала получить ссылку на него в поле, а затем перечислить эту ссылку , а затем убедитесь, что вы выбрали поле. В противном случае вы существенно «двойной расставить» (смотри ниже):

using xl = Microsoft.Office.Interop.Excel; 
... 
public void DoStuff() { 
    ... 
    xl.Sheets sheets = book.Sheets; 
    bool sheetsReleased = false; 
    try { 
     ... 
     foreach (xl.Sheet in sheets) { ... try, catch and dispose of sheet ... } 
     ... release sheets using Marshal.ReleaseComObject ... 
     sheetsDisposed = true; 
    } 
    catch (blah) { ... if !sheetsDisposed , dispose of sheets ... } 
} 

Приведенный выше код является общая картина (она становится длительным, если ввести его в полном объеме, так что я только сосредоточен на важных частях)

Как насчет ошибок?

Будьте бдительны в использовании try ... catch ... finally. Убедитесь, что вы используете это очень осторожно. finallyне всегда получает вызов в случае вещей, таких как переполнение стека, из памяти, исключения безопасности, поэтому, если вы хотите, чтобы вы очистили, и не оставляйте экземпляры фантомного excel открытыми, если ваш код выходит из строя, тогда вы должны условно выполнять освобождение ссылки в улове до того, как будут выброшены исключения.

Таким образом, внутри каждой foreach или for петли, вы также должны использовать try ... catch ... finally, чтобы убедиться, что переменная перечисления освобождается.

двойной пунктир

также не "double dot" (only use a single period in lines of code). Выполнение этого в foreach является распространенной ошибкой, которую нам легко сделать. Я все еще поймаю себя на том, чтобы сделать это, если я не выполняю не-COM C# некоторое время, поскольку все чаще и чаще встречаются цепочки периодов вместе из-за выражений стиля LINQ.

Примеров двойных расставить:

  • item.property.propertyIWant
  • item.Subcollection[0] (вы звоните субколлекции раньше вызов свойства индексатора по этой подколлекции)
  • foreach x in y.SubCollection (по существу, вы звоните SubCollection.GetEnumerator, так что вы " двойной пунктир ")

Phantom Ex cel

Большой тест, конечно же, заключается в том, чтобы проверить, остается ли Excel открытым в диспетчере задач после выхода программы. Если это произойдет, вы, вероятно, оставите сообщение COM открытым.

Ссылки

Вы говорите, что вы исследовали это тяжело, но в случае, если это помогает, то некоторые ссылки, которые я нашел полезным было:

Надежных решения

Одним из приведенных выше ссылок упоминают помощник он использует для foreach петель. Лично, если я делаю больше, чем простой проект «сценарий», я сначала потрачу время на разработку библиотеки, которая специально обертывает COM-объекты для моего сценария. Теперь у меня есть общий набор классов, которые я повторно использую, и я обнаружил, что время, потраченное на настройку этого, прежде чем делать что-либо еще, больше, чем вызвано тем, что вам не нужно выслеживать закрытые ссылки позже. Автоматическое тестирование также важно, чтобы помочь в этом, и получает вознаграждение за любой COM-взаимодействие, а не только за Excel.

Каждый COM-объект, такой как Sheet, будет завернут в класс, который реализует IDisposable. Он будет показывать такие свойства, как Sheets, который, в свою очередь, имеет индекс. Собственность отслеживается до конца, и в конце, если вы просто распоряжаетесь основным объектом, например WorkbookWrapper, тогда все остальное будет удалено изнутри. Например, добавление листа отслеживается, поэтому новый лист также будет удален.

Хотя это не пуленепробиваемый подход, вы можете, по крайней мере, полагаться на него на 95% случаев использования, а остальные 5%, о которых вы полностью осведомлены и позаботились в коде. Самое главное, он проверяется и может использоваться повторно, как только вы это сделали в первый раз.

+0

Спасибо Энди. Ваш ответ вдохновляет на размышления в угловых случаях, а также раскрывает некоторые аспекты COM Interop, о которых я не знал. Эти статьи также весьма полезны. Конечно, мой производственный код принимает намного больше мер, чем показано в коде в этом вопросе, но все же оставляет случайные «двойные точки» и т. Д. –

Смежные вопросы