Работа с объектами .NET IDisposable

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

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

EDIT:

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

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

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

EDIT:

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

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

[Отключается, отвращение]

Ответ 1

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'    

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Вот почему (IMHO) С++ RAII превосходит оператор .NET using.

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

Как и Fxcop (к которым они относятся), инструменты анализа кода в VS (если у вас есть один из выпусков с более высоким выпуском) также найдут эти случаи.

Ответ 9

Всегда старайтесь использовать блоки "использования". Для большинства объектов это не имеет большого значения, однако я столкнулся с недавней проблемой, когда я реализовал элемент управления ActiveX в классе и не очистил изящно, если Dispose не был вызван правильно. Суть в том, что даже если это не имеет особого значения, попробуйте сделать это правильно, потому что некоторое время это будет иметь значение.

Ответ 10

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

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

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

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

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

Ответ 11

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

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

Ответ 12

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

Я использую для создания классов 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, он будет вызван и объект будет очищен.