Я работаю на C#, и я довольно небрежно относился к использованию блоков using для объявления объектов, реализующих IDisposable, что вы, очевидно, всегда должны делать. Однако я не вижу простого способа узнать, когда я ошибаюсь. Visual Studio, похоже, никоим образом не указывает на это (я что-то упускаю?). Должен ли я просто проверять справку каждый раз, когда что-то объявляю, и постепенно накапливать энциклопедическую память о том, какие объекты являются одноразовыми, а какие нет? Кажется ненужным, болезненным и подверженным ошибкам.
Как ты справляется с этим?
Обновлено:
Глядя на боковую панель связанных вопросов, я обнаружил Другой вопрос, который дал понять, что Dispose() в любом случае должен вызываться финализатором объекта. Так что, даже если вы никогда не назовете это самостоятельно, в конечном итоге это должно произойти, а это означает, что у вас не будет утечки памяти, если вы не будете использовать using (это то, о чем я действительно беспокоился все время). Единственное предостережение заключается в том, что сборщик мусора не знает, сколько дополнительной памяти удерживается объектом как неуправляемый материал, поэтому он не будет иметь точного представления о том, сколько памяти будет освобождено путем сбора объекта. Это приведет к менее идеальной, чем обычно, производительности сборщика мусора.
Короче говоря, это не конец света, если я пропущу using. Я просто хочу, чтобы что-то генерировало хотя бы предупреждение об этом.
(Не по теме: почему нет специальной уценки за ссылку на другой вопрос?)
Обновлено:
Ладно, перестань кричать. Это супер-пупер с полным запуском драматический бурундук-level важный для вызова Dispose(), или мы все умереть.
Теперь. Учитывая это, почему так легко - черт возьми, почему даже позволил - сделать это неправильно? Вы должны изо всех сил делать это правильно. Выполнение этого, как и все остальное, приводит (очевидно) к армагедону. Так много для инкапсуляции, да?
[С отвращением уходит прочь]
@Atario - вам действительно стоит подумать об изменении принятого ответа; честно говоря, Тимви ошибается - или, в лучшем случае, сильно упрощает вещи до такой степени, что два (неправильное против упрощенного) очень близки.
Вы должны вызвать dispose как можно скорее, потому что он может освободить ресурсы задолго до завершения следующей сборки мусора. Если объект использует ресурс на вашем сервере или рабочем столе, который будет выпущен с помощью Dispose (), вы действительно хотите дождаться, пока сборщик мусора доберется до него?





FxCop мощь help (хотя он не обнаружил тест, который я только что запустил); но да: вы должны проверить. IDisposable - это просто такая важная часть системы, что вам нужно выработать эту привычку. Использование intellisense для поиска .D - хорошее начало (хотя и не идеальное).
Тем не менее, вам не понадобится много времени, чтобы ознакомиться с типами, требующими утилизации; вообще все, что связано с чем-либо внешним (например, соединение, файл, база данных).
ReSharper тоже выполняет эту работу, предлагая опцию «вставить в использование конструкции». Однако это не означает, что это ошибка ...
Конечно, если вы не уверены - пытатьсяusing it: компилятор насмехается над вами, если вы параноик:
using (int i = 5) {}
Error 1 'int': type used in a using statement must be implicitly convertible to 'System.IDisposable'
Как и Fxcop (с которым они связаны), инструменты анализа кода в VS (если у вас одна из вышестоящих версий) также найдут эти случаи.
На самом деле, я только что попробовал и FxCop, и анализ VSTS; ни один не заметил пропущенного "использования" в тривиальном тесте ...
Всегда старайтесь использовать блоки "использования". Для большинства объектов это не имеет большого значения, однако я столкнулся с недавней проблемой, когда я реализовал элемент управления ActiveX в классе и не выполнял очистку изящно, если Dispose не был вызван правильно. Суть в том, что даже если кажется, что это не имеет большого значения, постарайтесь сделать это правильно, потому что через некоторое время это будет иметь значение.
Это классический способ решения проблем с взаимодействием COM / ActiveX. Это, безусловно, аргумент в пользу того, чтобы убрать эти объекты с помощью using. Это не обязательно аргумент в пользу наведения порядка в объекте каждый с помощью using.
Я не знаю, могу ли я назвать это «обходным путем»; это скорее очистка неуправляемых ресурсов, которые могут привести к сбою или иным причинам. Что касается «аргумента для наведения порядка в каждом объекте» - я считаю, что вы всегда должны .Dispose объекта, реализующего IDisposable, если нет очень веской причины в противном случае, и блок «using» обычно является самым простым механизмом для этого. Это не совсем аргумент, просто мое мнение. Другие, например Ответ Скотта Дормана больше походил на попытку аргументации.
Если объект реализует интерфейс IDisposable, то для этого есть причина, и вы должны вызывать его, и его не следует рассматривать как необязательный. Самый простой способ сделать это - использовать блок using.
Dispose() не предназначен для вызова только финализатором объекта, и, фактически, многие объекты будут реализовывать Dispose(), но не финализатор (что совершенно верно).
Вся идея, лежащая в основе шаблона удаления, заключается в том, что вы предоставляете несколько детерминированный способ освобождения ресурсов неуправляемый, поддерживаемых объектом (или любым объектом в его цепочке наследования). Не вызывая Dispose() должным образом, вы абсолютно может столкнетесь с утечкой памяти (или любым другим количеством других проблем).
Метод Dispose() никак не связан с деструктором. Наиболее близким к деструктору в .NET является финализатор. Оператор using не выполняет никакого освобождения ... фактически, вызов Dispose() не освобождает управляемую кучу; он освобождает только неуправляемые ресурсы, которые были выделены. Управляемые ресурсы не освобождаются по-настоящему до тех пор, пока сборщик мусора не запустится и не соберет пространство памяти, выделенное для этого графа объектов.
Лучшие способы определить, реализует ли класс IDisposable:
Dispose() или Close())IDisposable, вы получите ошибку компилятора)Open(), вероятно, должен быть вызван соответствующий Close())using. Если он не реализует IDisposable, компилятор выдаст ошибку.Думайте о шаблоне удаления как об управлении жизненным циклом области действия. Вы хотите получить ресурс как можно дольше, использовать как можно быстрее и освободить как можно скорее. Оператор using помогает сделать это, гарантируя, что вызов Dispose() будет выполнен, даже если есть исключения.
Метод является.Dispose() тесно связан с деструктором - финализатор - это то, чем отличается. Вы используете Dispose для того же детерминированного освобождения памяти в стиле RAII, в котором используются деструкторы, например. C++.
@Atario, неверен не только принятый ответ, но и ваше собственное редактирование. Представьте себе следующую ситуацию (что на самом деле произошло в одной CTP из Visual Studio 2005):
Для рисования графики вы создаете перья, не выбрасывая их. Перьям не требуется много памяти, но они используют дескриптор GDI + внутри. Если вы не выбросите перо, ручка GDI + не будет выпущена. Если ваше приложение не требует интенсивного использования памяти, некоторое время может пройти без вызова GC. Однако количество доступных дескрипторов GDI + ограничено, и достаточно скоро, когда вы попытаетесь создать новое перо, операция завершится ошибкой.
Фактически, в Visual Studio 2005 CTP, если вы использовали приложение достаточно долго, все шрифты внезапно переключались на «Системные».
Именно поэтому недостаточно полагается на сборщик мусора при утилизации. Использование памяти не обязательно соответствует количеству неуправляемых ресурсов, которые вы приобретаете (и не выпускаете). Следовательно, эти ресурсы могут быть исчерпаны задолго до вызова GC.
Вдобавок, конечно, есть все аспекты побочных эффектов, которые могут иметь эти ресурсы (например, блокировки доступа), которые мешают другим приложениям работать должным образом.
Вот почему (IMHO) RAII C++ превосходит оператор using .NET.
Многие люди сказали, что IDisposable предназначен только для неуправляемых ресурсов, это верно только в зависимости от того, как вы определяете «ресурс». У вас может быть блокировка чтения / записи, реализующая IDisposable, и тогда «ресурс» - это концептуальный доступ к блоку кода. У вас может быть объект, который изменяет курсор на песочные часы в конструкторе и обратно к ранее сохраненному значению в IDispose, а затем «ресурс» - это измененный курсор. Я бы сказал, что вы используете IDisposable, когда хотите, чтобы детерминированное действие выполнялось при выходе из области видимости независимо от того, как она оставлена, но я должен признать, что это гораздо менее привлекательно, чем фраза «это для управления неуправляемым управлением ресурсами».
См. Также вопрос о почему в .NET нет RAII.
Финализаторы предназначены для неуправляемого управления ресурсами; IDisposable предназначен для управления ресурсами детерминированный. Обычно для неуправляемого ресурса требуются и то, и другое, но можно использовать IDisposable независимо от неуправляемого кода.
Вы НЕ должны использовать IDisposable для изменения курсора или чего-либо еще. Это чисто для освобождения неуправляемых ресурсов. Как говорит Эрик Липперт, вы «используете это не по назначению». Прочтите его комментарии на blogs.msdn.com/ericlippert/archive/2009/03/06/…
В каком смысле курсор не является «неуправляемым ресурсом»? У каждого элемента управления есть один, и когда вы меняете его, вы вступаете в борьбу с другими людьми, которые хотят, чтобы он был изменен иначе, и если он «просочился», курсор остается в неправильном состоянии.
К сожалению, ни FxCop, ни StyleCop, похоже, не предупреждают об этом. Как отмечали другие комментаторы, обычно очень важно обязательно вызвать dispose. Если я не уверен, я всегда проверяю обозреватель объектов (Ctrl + Alt + J), чтобы посмотреть на дерево наследования.
Я использую блоки using в основном для этого сценария:
Я использую какой-то внешний объект (в моем случае обычно обернутый COM-объект IDisposable). Состояние самого объекта может привести к тому, что он вызовет исключение, или то, как он влияет на мой код, может заставить меня выбросить исключение, и, возможно, во многих разных местах. В общем, я не доверяю никакому коду, выходящему за рамки моего текущего метода, вести себя правильно.
В качестве аргумента допустим, что у меня есть 11 точек выхода для моего метода, 10 из которых находятся внутри этого блока using и 1 после него (что может быть типичным для некоторого написанного мной библиотечного кода).
Поскольку объект автоматически удаляется при выходе из блока using, мне не нужно иметь 10 разных вызовов .Dispose () - это просто происходит. Это приводит к более чистому коду, поскольку теперь он менее загроможден вызовами dispose (в данном случае на 10 строк кода меньше).
Также существует меньший риск появления ошибок утечки IDisposable (на поиск которых может потребоваться много времени) из-за того, что кто-то изменит код после меня, если они забудут вызвать dispose, потому что в этом нет необходимости с использованием блока.
Мне действительно нечего добавить к общему использованию блоков Using, но я просто хотел добавить исключение к правилу:
Любой объект, реализующий IDisposable, по-видимому, не должен вызывать исключение во время его метода Dispose (). Это отлично работало до WCF (могут быть и другие), и теперь возможно, что канал WCF выбрасывает исключение во время Dispose (). Если это происходит, когда он используется в блоке Using, это вызывает проблемы и требует реализации обработки исключений. Это, очевидно, требует большего знания внутренней работы, поэтому Microsoft теперь рекомендует не использовать каналы WCF в блоках Using (извините, не удалось найти ссылку, но много других результатов в Google), даже если он реализует IDisposable .. Просто чтобы сделать вещи более сложный!
In short, it's not the end of the world if I miss a using. I just wish something would generate at least a warning for it.
Проблема здесь в том, что вы не всегда можете справиться с IDisposable, просто заключив его в блок using. Иногда вам нужно, чтобы объект оставался немного дольше. В этом случае вам придется явно вызвать его метод Dispose самостоятельно.
Хорошим примером этого является класс использует частный EventWaitHandle (или AutoResetEvent) для связи между двумя потоками, и вы хотите удалить WaitHandle после завершения потока.
Так что это не так просто, как какой-нибудь инструмент, просто проверить, что вы создаете только объекты IDisposable в блоке using.
Согласно эта ссылка, Надстройка CodeRush будет обнаруживать и отмечать, когда локальные переменные IDisposable не очищаются, в режиме реального времени по мере ввода.
Могу встретить вас на полпути квесту.
Я не понимаю сути вашего вопроса. Благодаря сборщику мусора утечки памяти практически невозможны. Однако вам нужна надежная логика.
Я использую для создания классов 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, он в конечном итоге будет вызван, и объект будет очищен.
-1 для менее безопасного связанного примера. код имеет как минимум 2 проблемы: 1) доступ к управляемым объектам осуществляется из потока финализатора, поскольку он не различает правильное удаление и завершение; 2) он не является потокобезопасным, поскольку метод удаления не выполняет атомарную проверку и не устанавливает флаг удаления ( и из-за проблемы №1 код стал многопоточным, поскольку финализатор работает в собственном потоке)
да. В коде, который вы публикуете здесь в своем ответе, у вас есть чек if (disposing) { }, но в коде, который вы связали с ним, такой проверки нет. Таким образом, он делает то же самое, независимо от того, был ли он вызван программистом, правильно утилизирующим его, или сборщиком мусора, вызвавшим финализатор.
Фактически, программист (мой!) Намеревался сделать то же самое в обоих случаях. Этот файл вызывает веб-службу для прекращения подписки на насос сообщений UDP до истечения времени ожидания. Я связал код не для отображения примера упомянутого оператора если, а для того, чтобы показать, как финализатор должен использоваться для удаления ресурсов на уровне бизнес-логики. Вы упомянули управляемые объекты, к которым осуществляется доступ из потока финализатора: что-то не так, а не параллелизм?
Я снова посмотрел на ваш пример. Игнорирование потенциального сбоя из-за состояния гонки финализатора, есть еще одна ключевая проблема. Класс ClientBase не управляет никакими собственными ресурсами (например, дескриптором GDI, сокетом и т. д.). Единственная цель финализаторов - освободить собственные ресурсы. Если вы не освобождаете собственные ресурсы, у вас не должно быть финализатора. ChannelManager может владеть собственными ресурсами, и поэтому ChannelManager может реализовывать финализатор. Но ClientBase не владеет собственными ресурсами напрямую и поэтому не должен реализовывать финализатор.
Причина, по которой Microsoft не должна касаться управляемых объектов в финализаторе, кратко объясняется здесь. msdn.microsoft.com/en-us/library/ddae83kx.aspx Превосходная статья Джо Даффи разъяснила мне эти важные моменты. bluebytesoftware.com/blog/…
Извините за все комментарии, но я понял, что вы хотели, чтобы я обратился к пункту №2. В вашем Dispose() вы проверяете наличие if (Disposed) в начале вашего метода. Позже, после некоторых действий по очистке, вы устанавливаете флаг Disposed на true. Пока метод Dispose выполняется в одном потоке, он может быть вызван снова в другом потоке. Если «первый» поток все еще работает над очисткой, тогда флаг Disposed будет ложным, и «второй» поток также начнет действия по очистке. Если вы можете гарантировать, что вызываете Dispose только из одного потока, то все в порядке.
Вы не найдете на планете ни одного опытного программиста .net, который согласился бы с вами. Я не хочу быть мелодраматичным, но вам действительно стоит подобрать хорошую книгу .Net.