Следует ли установить для всех объектов значение null
(Nothing
в VB.NET) после того, как вы закончите с ними?
Я понимаю, что в .NET важно избавляться от любых экземпляров объектов, реализующих интерфейс IDisposable
, для высвобождения некоторых ресурсов, хотя объект все еще может быть чем-то после его удаления (следовательно, свойство isDisposed
в формах), поэтому я предполагаю, что он может все еще хранятся в памяти или хотя бы частично?
Я также знаю, что когда объект выходит за пределы области видимости, он помечается для сбора и готов к следующему проходу сборщика мусора (хотя это может занять время).
Таким образом, имея это в виду, установка null
ускорит высвобождение памяти системой, поскольку ей не нужно понимать, что она больше не входит в область действия, и есть ли у них какие-либо плохие побочные эффекты?
Статьи MSDN никогда не делают этого в примерах, и в настоящее время я делаю это, поскольку не могу увидеть вред. Однако я встречал разные мнения, поэтому любые комментарии полезны.
Некоторые объекты предполагают использование метода .dispose()
, который принудительно удаляет ресурс из памяти.
Нет, это не так; Dispose () собирает объект нет - он используется для выполнения детерминированной очистки, обычно освобождая неуправляемые ресурсы.
Принимая во внимание, что детерминизм применяется только к управляемым ресурсам, а не к неуправляемым (то есть к памяти)
Нет, не обнуляйте объекты. Вы можете проверить https://web.archive.org/web/20160325050833/http://codebetter.com/karlseguin/2008/04/28/foundations-of-programming-pt-7-back-to-basics-memory/ для получения дополнительной информации, но установка значения null ничего не сделает, кроме загрязнения вашего кода.
Красивое и подробное объяснение памяти в общей ссылке
Ссылка не работает. Без связанного контента этот ответ бесполезен и должен быть удален.
Единственный раз, когда вы должны установить для переменной значение null, - это когда переменная не выходит за пределы области видимости и вам больше не нужны связанные с ней данные. Иначе в этом нет необходимости.
Это правда, но это также означает, что вам, вероятно, следует реорганизовать свой код. Не думаю, что мне когда-либо приходилось объявлять переменную за пределами предполагаемой области видимости.
Если понимать, что «переменная» включает поля объекта, тогда этот ответ имеет большой смысл. В случае, когда «переменная» означает только «локальную переменную» (метода), тогда мы, вероятно, говорим здесь о нишевых случаях (например, метод, который выполняется в течение гораздо более длительного, чем обычно, промежутка времени).
Также:
using(SomeObject object = new SomeObject())
{
// do stuff with the object
}
// the object will be disposed of
В некоторых случаях имеет смысл использовать пустые ссылки. Например, когда вы пишете коллекцию - например, очередь с приоритетом - и по вашему контракту, вы не должны сохранять эти объекты живыми для клиента после того, как клиент удалил их из очереди.
Но такие вещи имеют значение только в долгоживущих коллекциях. Если очередь не переживет конец функции, в которой она была создана, это имеет гораздо меньшее значение.
В общем, не стоит беспокоиться. Пусть компилятор и сборщик мусора делают свою работу, а вы можете делать свою.
Карл абсолютно прав, нет необходимости обнулять объекты после использования. Если объект реализует IDisposable
, просто убедитесь, что вы вызываете IDisposable.Dispose()
, когда закончите с этим объектом (заключенным в try
..finally
или блок using()
). Но даже если вы не забыли вызвать Dispose()
, метод финализатора объекта должен вызывать Dispose()
за вас.
Я подумал, что это хорошее лечение:
и это
Нет никакого смысла пытаться переубедить сборщика мусора и его стратегии управления, потому что он самонастраивающийся и непрозрачный. Здесь была хорошая дискуссия о внутренней работе с Джеффри Рихтером на Dot Net Rocks: Джеффри Рихтер о модели памяти Windows и В книге Рихтерса CLR через C# глава 20 есть отличная трактовка:
Правило не устанавливать значение NULL не является «жестким и быстрым» ... если объект помещается в кучу больших объектов (размер> 85K), сборщик мусора может помочь, если вы установите для объекта значение NULL, когда закончите. используй это.
Я согласен в ограниченной степени, но если вы не начинаете испытывать нехватку памяти, тогда я не вижу необходимости «преждевременно оптимизировать», устанавливая для объектов значение null после использования.
Вся эта идея «не оптимизировать преждевременно» больше похожа на «Предпочитайте медленные и не беспокойтесь, потому что процессоры становятся быстрее, а приложениям CRUD скорость все равно не нужна». Хотя это может быть только я. :)
На самом деле это означает: «Сборщик мусора лучше справляется с управлением памятью, чем вы». Хотя это может быть только я. :)
Как насчет того, чтобы удалить объект из ссылки в месте, отличном от исходного (например, в другой форме), а позже вы захотите проверить по исходной ссылке, был ли удален объект?
@BobbyShaftoe: Сказать «преждевременная оптимизация - это всегда плохо», наверное, так же неправильно, как и переход к противоположной крайности «больше похоже на« предпочитаю медленно »». Ни один разумный программист тоже не скажет. Речь идет о нюансах и умном подходе к оптимизации. Я лично беспокоился бы о ясности кода и ПОТОМ ДЕЙСТВИТЕЛЬНО ПРОТЕСТИРУЙТЕ производительность, поскольку я лично видел, как много людей (включая меня, когда я был моложе) тратили слишком много времени на создание «идеального» алгоритма, только чтобы сэкономить 0,1 мс. всего за 100 000 итераций, при этом читаемость была полностью снижена.
Хорошо, я понял, установка объектов, свойств и переменных на null не заставляет GC работать быстрее и, таким образом, удалять значения из памяти, но не обеспечит ли он уровень обфускации, который удалит конфиденциальные данные его контекста, т.е. эта 16-символьная строка без ссылки в куче "1234567890123456" пароль, номер кредитной карты, номер телефона и т. д.?
@VFein - это не работа GC. Вы, разработчик, несете ответственность за защиту / шифрование того, что хранится в памяти. Кроме того, если ваша машина настолько сильно скомпрометирована, что злоумышленник может прочитать произвольные участки памяти, игра уже окончена.
В общем, нет необходимости обнулять объекты после использования, но в некоторых случаях я считаю это хорошей практикой.
Если объект реализует IDisposable и хранится в поле, я думаю, что хорошо обнулить его, просто чтобы избежать использования удаленного объекта. Ошибки следующего вида могут быть болезненными:
this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();
Хорошо обнулить поле после его удаления и получить NullPtrEx прямо в строке, где поле снова используется. В противном случае вы можете столкнуться с какой-то загадочной ошибкой (в зависимости от того, что именно делает DoSomething).
Итак, удаленный объект должен вызывать исключение ObjectDisposedException, если он уже удален. Насколько я знаю, это требует повсеместного использования шаблонного кода, но, опять же, Disposed - это плохо продуманная парадигма.
Ctrl + F для .Dispose()
. Если вы его нашли, значит, вы неправильно используете IDisposable. Единственное использование одноразового объекта должно быть в пределах блока использования. А после блока using у вас больше не будет доступа к myField
. А внутри блока using установка null
не требуется, блок using избавит вас от объекта.
Скорее всего, ваш код структурирован недостаточно четко, если вы чувствуете необходимость в переменных null
.
Есть несколько способов ограничить область действия переменной:
Как упоминалось Стив Транби
using(SomeObject object = new SomeObject())
{
// do stuff with the object
}
// the object will be disposed of
Точно так же вы можете просто использовать фигурные скобки:
{
// Declare the variable and use it
SomeObject object = new SomeObject()
}
// The variable is no longer available
Я считаю, что использование фигурных скобок без «заголовка» действительно помогает очистить код и сделать его более понятным.
Однажды я пробовал использовать пользовательские локальные прицелы (в основном это smarta $$). Компания взорвалась.
Еще одно замечание: это связано с тем, что компилятор C# найдет переменные с локальной областью видимости, которые реализуют IDisposable, и вызовет .Dispose (В большинстве случаев), когда их область видимости закончится. Однако ... Соединения SQL - это большое время, когда .Dispose () никогда не оптимизируется. Есть некоторые типы, требующие особого внимания, поэтому я лично всегда делаю что-то явно, чтобы меня не укусили.
Еще одна причина, по которой не следует устанавливать для объектов значение null, когда вы закончите с ними, заключается в том, что это может продлить их жизнь.
например
void foo()
{
var someType = new SomeType();
someType.DoSomething();
// someType is now eligible for garbage collection
// ... rest of method not using 'someType' ...
}
позволит объекту, на который указывает someType, быть GC после вызова "DoSomething", но
void foo()
{
var someType = new SomeType();
someType.DoSomething();
// someType is NOT eligible for garbage collection yet
// because that variable is used at the end of the method
// ... rest of method not using 'someType' ...
someType = null;
}
может иногда сохранять объект в живых до конца метода. JIT обычно оптимизирует присвоение null, поэтому оба бита кода в конечном итоге совпадают.
Это интересный момент. Я всегда думал, что объекты не выходят из области видимости до тех пор, пока не будет завершен метод, в котором они находятся. Если, конечно, объект не находится в пределах блока Using или явно не установлен в Nothing или null.
Предпочтительный способ убедиться, что они остаются в живых, - использовать GC.KeepAlive(someType);
. См. ericlippert.com/2013/06/10/construction-destruction.
Взгляните также на эту статью: http://www.codeproject.com/KB/cs/idisposable.aspx
По большей части установка объекта на null не имеет никакого эффекта. Обязательно делать это только в том случае, если вы работаете с «большим объектом», размер которого превышает 84 КБ (например, растровые изображения).
такой вид «нет необходимости устанавливать для объектов значение NULL после использования» не совсем точен. Иногда вам нужно обнулить переменную после ее удаления.
Да, вы должны ВСЕГДА звонить в .Dispose()
или .Close()
по всем вопросам, в которых он есть, когда закончите. Будь то дескрипторы файлов, подключения к базе данных или одноразовые объекты.
Отдельно от этого очень практичный шаблон LazyLoad.
Скажем, у меня есть и создан экземпляр ObjA
из class A
. Class A
имеет публичную собственность под названием PropB
из class B
.
Внутри PropB
использует частную переменную _B
и по умолчанию имеет значение null. Когда используется PropB.Get()
, он проверяет, является ли _PropB
нулевым, и, если это так, открывает ресурсы, необходимые для создания экземпляра B
в _PropB
. Затем он возвращает _PropB
.
По моему опыту, это действительно полезный трюк.
Когда возникает необходимость в null, если вы сбрасываете или изменяете A каким-либо образом, чтобы содержимое _PropB
было дочерним по отношению к предыдущим значениям A
, вам нужно будет Dispose И обнулить _PropB
, чтобы LazyLoad мог сбросить для получения правильного значения ЕСЛИ код требует этого.
Если вы выполняете только _PropB.Dispose()
и вскоре после этого ожидаете, что проверка на null для LazyLoad завершится успешно, она не будет нулевой, и вы будете смотреть на устаревшие данные. Фактически, вы должны обнулить его после Dispose()
, чтобы быть уверенным.
Я бы хотел, чтобы это было иначе, но у меня сейчас есть код, демонстрирующий это поведение после Dispose()
на _PropB
и за пределами вызывающей функции, которая выполнила Dispose (и, следовательно, почти вне области действия), частная опора все еще не null, а устаревшие данные все еще там.
В конце концов, свойство disposed обнулится, но с моей точки зрения это было недетерминированным.
Основная причина, как указывает dbkk, заключается в том, что родительский контейнер (ObjA
с PropB
) сохраняет в области действия экземпляр _PropB
, несмотря на Dispose()
.
Хороший пример, показывающий, как установка значения null вручную означает более фатальную ошибку для вызывающего, что хорошо.
В общем случае нет необходимости устанавливать значение null. Но предположим, что в вашем классе есть функция сброса.
Тогда вы можете это сделать, потому что вы не хотите вызывать dispose дважды, поскольку некоторые из Dispose могут быть реализованы неправильно и вызывать исключение System.ObjectDisposed.
private void Reset()
{
if (_dataset != null)
{
_dataset.Dispose();
_dataset = null;
}
//..More such member variables like oracle connection etc. _oraConnection
}
Лучше всего просто отслеживать это с помощью отдельного флажка.
Я считаю, что по замыслу разработчиков GC вы не можете использовать ускориться GC с обнулением. Я уверен, что они предпочли бы, чтобы вы не беспокоились о том, как и когда работает сборщик мусора - относитесь к нему как к этому вездесущему Существование, который защищает и наблюдает за вами ... (склоняет голову, поднимает кулак к небу) .. .
Лично я часто явно устанавливаю для переменных значение null, когда заканчиваю с ними, как форму самодокументирования. Я не объявляю, использую, а затем устанавливаю значение null позже - я обнуляю сразу после того, как они больше не нужны. Я прямо говорю: «Я официально с тобой покончил ... уходи ...»
Обнуление необходимо в языке GC? Нет. Это полезно для GC? Может быть, да, может быть, нет, не знаю наверняка, по замыслу я действительно не могу это контролировать, и независимо от сегодняшнего ответа с той или иной версией будущие реализации GC могут изменить ответ вне моего контроля. Плюс, если / когда обнуление оптимизировано, это немного больше, чем причудливый комментарий, если хотите.
Я полагаю, если это проясняет мои намерения для следующего бедняги, который пойдет по моим стопам, и если "мог бы" потенциально может иногда помочь GC, то для меня оно того стоит. В основном это заставляет меня чувствовать себя опрятно и ясно, а Монго любит чувствовать себя опрятным и чистым. :)
Я смотрю на это так: существуют языки программирования, позволяющие людям дать другим людям представление о намерениях, а компилятору - запрос задания о том, что делать - компилятор преобразует этот запрос на другой язык (иногда несколько) для ЦП - ЦП могут дать понять, какой язык вы использовали, настройки вкладок, комментарии, стилистические акценты, имена переменных и т. д. - ЦП все о потоке битов, который сообщает ему, какие регистры, коды операций и места в памяти нужно вертеть. Многие вещи, написанные в коде, не преобразуются в то, что потребляет ЦП в указанной нами последовательности. Наши C, C++, C#, Lisp, Babel, ассемблер или что-то еще является теорией, а не реальностью, написанной как техническое задание. То, что вы видите, - это не то, что вы получаете, да, даже на языке ассемблера.
Я понимаю, что «ненужные вещи» (например, пустые строки) «не что иное, как шум и беспорядок в коде». Это был я раньше в моей карьере; Я полностью понимаю это. На данном этапе я склоняюсь к тому, что делает код более понятным. Это не значит, что я добавляю в свои программы даже 50 строк «шума» - это несколько строк здесь или там.
Из любого правила есть исключения. В сценариях с энергозависимой памятью, статической памятью, условиями гонки, одиночными копиями, использованием «устаревших» данных и всем подобным гнилью все по-другому: вам НЕОБХОДИМО управлять своей собственной памятью, блокируя и аннулируя, по возможности, потому что память не является частью Вселенная GC'd - надеюсь, все это понимают. В остальное время с языками GC это вопрос стиля, а не необходимости или гарантированного повышения производительности.
В конце концов, убедитесь, что вы понимаете, что подходит для GC, а что нет; блокировать, удалять и аннулировать соответствующим образом; воск на, воск прочь; вдох-выдох; и обо всем остальном я говорю: если тебе нравится, делай это. Ваш пробег может отличаться ... как и должно ...
Стивен Клири очень хорошо объясняет в этом посте: Должен ли я установить для переменных значение NULL, чтобы облегчить сборку мусора?
Говорит:
The Short Answer, for the Impatient Yes, if the variable is a static field, or if you are writing an enumerable method (using yield return) or an asynchronous method (using async and await). Otherwise, no.
This means that in regular methods (non-enumerable and non-asynchronous), you do not set local variables, method parameters, or instance fields to null.
(Even if you’re implementing IDisposable.Dispose, you still should not set variables to null).
Важная вещь, которую мы должны учитывать, - это Статические поля.
Static fields are always root objects, so they are always considered “alive” by the garbage collector. If a static field references an object that is no longer needed, it should be set to null so that the garbage collector will treat it as eligible for collection.
Setting static fields to null is meaningless if the entire process is shutting down. The entire heap is about to be garbage collected at that point, including all the root objects.
Вывод:
Static fields; that’s about it. Anything else is a waste of time.
Я думаю, что возвращать что-то к нулю беспорядочно. Представьте себе сценарий, в котором устанавливаемый сейчас элемент отображается, скажем, через свойство. Теперь каким-то образом какой-то фрагмент кода случайно использует это свойство после удаления элемента, вы получите исключение с нулевой ссылкой, которое требует некоторого исследования, чтобы точно выяснить, что происходит.
Я считаю, что одноразовые фреймворки позволяют генерировать ObjectDisposedException, что более значимо. По этой причине было бы лучше не возвращать их к нулю.
+1 отличный вопрос. Кто-нибудь знает обстоятельства, при которых компилятор полностью оптимизирует назначение? то есть кто-нибудь смотрел на MSIL при разных обстоятельствах и отмечал IL для установки объекта в null (или его отсутствия).