Работа с .NET IDisposable объектами - PullRequest
40 голосов
/ 01 ноября 2008

Я работаю в C #, и я довольно слабо использовал блоки using для объявления объектов, которые реализуют IDisposable, что вы, очевидно, всегда должны делать. Тем не менее, я не вижу простой способ узнать, когда я ускользнул. Visual Studio, кажется, не указывает на это каким-либо образом (я просто что-то упустил?). Должен ли я просто проверять помощь каждый раз, когда я что-либо заявляю, и постепенно создавать энциклопедическую память, для которой существуют предметы, а какие нет? Кажется ненужным, болезненным и подверженным ошибкам.

Как вы справляетесь с этим?

EDIT:

Глядя на боковую панель связанных вопросов, я обнаружил еще один вопрос , который дал понять, что Dispose() в любом случае должен вызываться финализатором объекта. Поэтому, даже если вы никогда не называете это сами, это должно произойти, то есть у вас не будет утечки памяти, если вы не используете using (что, я полагаю, меня все время волновало). Единственное предостережение в том, что сборщик мусора не знает, сколько дополнительной памяти удерживает объект как неуправляемый материал, поэтому он не будет точно представлять, сколько памяти будет освобождено при сборе объекта. Это приведет к тому, что сборщик мусора будет работать не так идеально, как обычно.

Короче говоря, это не конец света, если я пропущу using. Я просто хотел бы, чтобы что-то сгенерировало хотя бы предупреждение.

(Вне темы: почему нет специальной уценки для ссылки на другой вопрос?)

EDIT:

Хорошо, хорошо, прекрати требовать. Это супер пупер вселенный dramatic-chipmunk -уровень важно , чтобы позвонить Dispose(), или мы все умрем .

Теперь. Учитывая это, почему это так легко & mdash; черт, почему это даже разрешено & mdash; сделать это неправильно? Вы должны изо всех сил, чтобы сделать это правильно. Выполнение этого как все остальное приводит к Армагеддону (очевидно). Так много для инкапсуляции, да?

[Отвращение, отвращение]

Ответы [ 12 ]

24 голосов
/ 01 ноября 2008

FxCop может помочь (хотя он не обнаружил тест, который я только что запустил); но да: вы должны проверить. IDisposable просто настолько важная часть системы, что вам нужно войти в эту привычку. Использование intellisense для поиска .D - хорошее начало (хотя и не идеальное).

Однако, это не займет много времени, чтобы ознакомиться с типами, которые требуют утилизации; как правило, все, что связано с чем-либо внешним (соединение, файл, база данных), например.

ReSharper тоже делает эту работу, предлагая опцию «положить в использование конструкции». Хотя это не является ошибкой ...

Конечно, если вы не уверены - попробуйте using это: компилятор будет насмехаться над вами, если вы параноик:

using (int i = 5) {}

Error   1   'int': type used in a using statement must be implicitly convertible to 'System.IDisposable'    
15 голосов
/ 01 ноября 2008

Если объект реализует интерфейс IDisposable, то это по какой-то причине, и вы должны вызывать его, и он не должен рассматриваться как необязательный. Самый простой способ сделать это - использовать блок using.

Dispose() не предназначен для вызова только финализатором объекта, и, фактически, многие объекты будут реализовывать Dispose(), но не финализатор (что совершенно правильно).

Вся идея шаблона утилизации заключается в том, что вы предоставляете несколько детерминированный способ высвобождения неуправляемых ресурсов, поддерживаемых объектом (или любым объектом в его цепочке наследования). Не правильно вызвав Dispose(), вы абсолютно можете столкнуться с утечкой памяти (или с любым другим количеством проблем).

Метод Dispose() никоим образом не связан с деструктором. Ближайший деструктор в .NET - финализатор. Оператор using не выполняет никакого освобождения ... фактически вызов Dispose() не выполняет никакого освобождения в управляемой куче; он освобождает только неуправляемые ресурсы, которые были выделены. Управляемые ресурсы не освобождаются по-настоящему, пока GC не запустится и не соберет пространство памяти, выделенное для этого графа объектов.

Лучшие способы определить, реализует ли класс IDisposable:

  • IntelliSense (если он имеет метод Dispose() или Close())
  • 1028 * MSDN *
  • Отражатель
  • Компилятор (если он не реализует IDisposable, вы получите ошибку компилятора)
  • Здравый смысл (если он чувствует как будто вы должны закрыть / отпустить объект после того, как закончите, то вы, вероятно, должны )
  • Семантика (если есть Open(), вероятно, существует соответствующий Close(), который следует назвать)
  • Компилятор. Попробуйте поместить его в оператор using. Если он не реализует IDisposable, компилятор выдаст ошибку.

Думайте о шаблоне утилизации как об управлении временем жизни области. Вы хотите получить ресурс как можно дольше, использовать как можно быстрее и освободить как можно скорее. Использование оператора помогает сделать это, гарантируя, что вызов Dispose() будет выполнен, даже если есть исключения.

4 голосов
/ 19 марта 2009

Короче говоря, это не конец света, если я пропущу использование. Я просто хотел бы, чтобы что-то сгенерировало хотя бы предупреждение.

Проблема здесь в том, что вы не всегда можете иметь дело с IDisposable, просто заключив его в блок using. Иногда вам нужен объект, чтобы побыть немного дольше. В этом случае вам придется самостоятельно вызывать его метод Dispose.

Хорошим примером этого является случай, когда класс использует частный EventWaitHandle (или AutoResetEvent) для связи между двумя потоками, и вы хотите избавиться от WaitHandle после завершения потока.

Так что это не так просто, как какой-то инструмент, просто проверяющий, что вы создаете только IDisposable объекты внутри блока using.

4 голосов
/ 02 ноября 2008

Вот почему (ИМХО) RAII в C ++ превосходит оператор using в .NET.

Многие люди говорили, что IDisposable только для неуправляемых ресурсов, это верно только в зависимости от того, как вы определяете «ресурс». Вы можете иметь блокировку чтения / записи, реализующую IDisposable, и тогда «ресурс» - это концептуальный доступ к блоку кода. У вас может быть объект, который меняет курсор на песочные часы в конструкторе и возвращает к ранее сохраненному значению в IDispose, а затем «ресурс» является измененным курсором. Я бы сказал, что вы используете IDisposable, когда хотите, чтобы при выходе из области действия происходило детерминированное действие, независимо от того, как оставлена ​​область действия, но я должен признать, что это гораздо менее броско, чем сказать «это для управления неуправляемым управлением ресурсами».

См. Также вопрос о , почему в .NET нет RAII .

3 голосов
/ 02 ноября 2008

@ Atario, не только принятый ответ неверен, но и ваше собственное редактирование. Представьте себе следующую ситуацию (, которая фактически произошла в одном CTP Visual Studio 2005):

Для рисования графики вы создаете ручки без их утилизации. Ручки не требуют много памяти, но они используют дескриптор GDI + внутри. Если вы не утилизируете перо, ручка GDI + не будет выпущена. Если ваше приложение не потребляет много памяти, может пройти некоторое время без вызова GC. Однако количество доступных дескрипторов GDI + ограничено, и достаточно скоро, когда вы попытаетесь создать новое перо, операция не будет выполнена.

Фактически, в Visual Studio 2005 CTP, если вы использовали приложение достаточно долго, все шрифты внезапно переключались бы на «Системный».

Именно поэтому недостаточно , чтобы полагаться на ГХ для утилизации. Использование памяти не обязательно связано с количеством неуправляемых ресурсов, которые вы приобретаете (и не освобождаете). Следовательно, эти ресурсы могут быть исчерпаны задолго до вызова GC.

Кроме того, есть, конечно, все аспекты побочных эффектов, которые могут иметь эти ресурсы (например, блокировки доступа), которые мешают другим приложениям работать должным образом.

2 голосов
/ 05 ноября 2008

У меня нет ничего, что можно было бы добавить к общему использованию блоков Using, но я просто хотел добавить исключение из правила:

Любой объект, который реализует IDisposable, очевидно, не должен вызывать исключение во время его метода Dispose (). Это работало отлично до WCF (могут быть другие), и теперь возможно, что исключение выдается каналом WCF во время Dispose (). Если это происходит при использовании в блоке Using, это вызывает проблемы и требует реализации обработки исключений. Это, очевидно, требует больше знаний о внутренней работе, поэтому Microsoft теперь рекомендует не использовать каналы WCF в разделе Использование блоков (извините, не удалось найти ссылку, но множество других результатов в Google), даже если он реализует IDisposable. сложно!

1 голос
/ 02 ноября 2008

Я использую блоки в основном для этого сценария:

Я потребляю некоторый внешний объект (обычно в моем случае это обернутый IDispos-объект COM). Состояние самого объекта может привести к тому, что он сгенерирует исключение, или то, как оно влияет на мой код, может заставить меня сгенерировать исключение, и, возможно, во многих разных местах. В общем, я не доверяю никакому коду вне моего текущего метода, чтобы вести себя сам.

В качестве аргумента, скажем, у меня есть 11 точек выхода для моего метода, 10 из которых находятся внутри этого блока, а 1 после него (что может быть типичным для некоторого библиотечного кода, который я написал).

Поскольку объект автоматически удаляется при выходе из блока using, мне не нужно иметь 10 различных вызовов .Dispose () - это просто происходит. Это приводит к более чистому коду, поскольку теперь он менее загроможден вызовами dispose (в данном случае на ~ 10 строк кода меньше).

Также меньше риск появления IDisposable багов утечек (которые могут занять много времени, когда кто-то изменяет код после меня, если они забывают вызвать dispose, потому что в блоке using нет необходимости.

1 голос
/ 02 ноября 2008

К сожалению, ни FxCop, ни StyleCop не предупреждают об этом. Как уже упоминали другие комментаторы, обычно очень важно убедиться, что позвонил утилизировать. Если я не уверен, я всегда проверяю Object Browser (Ctrl + Alt + J), чтобы посмотреть на дерево наследования.

0 голосов
/ 19 января 2011

Я не понимаю твоего вопроса. Благодаря сборщику мусора утечки памяти практически невозможны. Однако вам нужна надежная логика.

Я использую для создания IDisposable классов, таких как:

public MyClass: IDisposable
{

    private bool _disposed = false;

    //Destructor
    ~MyClass()
    { Dispose(false); }

    public void Dispose()
    { Dispose(true); }

    private void Dispose(bool disposing)
    {
        if (_disposed) return;
        GC.SuppressFinalize(this);

        /* actions to always perform */

        if (disposing) { /* actions to be performed when Dispose() is called */ }

        _disposed=true;
}

Теперь, даже если вы пропустите использование оператора using, объект будет в конечном итоге собран мусором и будет выполнена правильная логика уничтожения. Вы можете останавливать потоки, завершать соединения, сохранять данные, что вам нужно (в в этом примере я отписываюсь от удаленной службы и при необходимости выполняю удаленный вызов удаления)

[Edit], очевидно, что вызов Dispose как можно скорее помогает производительности приложения, а - это хорошая практика. Но, благодаря моему примеру, если вы забудете вызвать Dispose, он будет в конечном итоге вызван и объект очищен.

0 голосов
/ 10 февраля 2010

Согласно эта ссылка Надстройка CodeRush будет обнаруживать и помечать, когда локальные переменные IDisposable не очищаются, в режиме реального времени при вводе.

Может встретиться с вами на полпути в вашем квесте.

...