Установка для объектов значения Null / Nothing после использования в .NET

Должны ли вы установить все объекты в null ( Nothing в VB.NET) после того, как закончите с ними?

Я понимаю, что в .NET важно избавляться от любых экземпляров объектов, которые реализуют IDisposable интерфейс для высвобождения некоторых ресурсов, хотя объект все еще может быть чем-то после его удаления (следовательно, isDisposed свойство в формах), поэтому я предполагаю, что он все еще может находиться в памяти или хотя бы частично?

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

Итак, имея это в виду, настроим его для null ускорения высвобождения памяти системой, поскольку ей не нужно решать, что она больше не входит в область действия, и есть ли у них какие-либо плохие побочные эффекты?

Статьи MSDN никогда не делают этого в примерах, и в настоящее время я делаю это, так как не вижу вреда. Однако я встречал разные мнения, поэтому любые комментарии полезны.

Ответов (15)

Решение

Карл абсолютно прав, нет необходимости обнулять объекты после использования. Если объект реализуется IDisposable, просто убедитесь, что вы вызываете его, IDisposable.Dispose() когда закончите с этим объектом (заключенным в try .. finally, или, using() блок). Но даже если вы не забыли вызвать Dispose(), метод финализатора объекта должен вызывать Dispose() вас.

Я подумал, что это хорошее лечение:

Копаемся в IDisposable

и это

Понимание IDisposable

Нет никакого смысла пытаться переубедить сборщика мусора и его стратегии управления, потому что он самонастраивающийся и непрозрачный. Здесь была хорошая дискуссия о внутренней работе с Джеффри Рихтером над Dot Net Rocks: Джеффри Рихтер о модели памяти Windows и книга Рихтерса CLR через C#, глава 20 содержит отличную трактовку:

такой вид «нет необходимости устанавливать для объектов значение NULL после использования» не совсем точен. Иногда вам нужно обнулить переменную после ее удаления.

Да, вы должны ВСЕГДА звонить .Dispose() или .Close() по любому поводу, когда вы закончите. Будь то дескрипторы файлов, подключения к базе данных или одноразовые объекты.

Отдельно от этого очень практичный шаблон LazyLoad.

Скажем , у меня есть и экземпляры ObjA из class A . Class A имеет общественное свойство PropB из class B .

Внутренне PropB использует частную переменную _B и по умолчанию имеет значение null. Когда PropB.Get() используется, он проверяет, если _PropB равно нулю , и если она есть, открывает ресурсы , необходимые для создания экземпляра B INTO _PropB . Затем он возвращается _PropB .

По моему опыту, это действительно полезный трюк.

Когда возникает необходимость в null, если вы сбрасываете или изменяете A каким-либо образом, чтобы содержимое _PropB было дочерним по отношению к предыдущим значениям A, вам нужно будет Dispose И _PropB обнулить , чтобы LazyLoad мог сбросить, чтобы получить правильное значение, ЕСЛИ код требует этого.

Если вы это сделаете _PropB.Dispose() и вскоре после этого ожидаете, что проверка на null для LazyLoad завершится успешно, она не будет нулевой, и вы будете смотреть на устаревшие данные. Фактически, вы должны обнулить его после, Dispose() чтобы быть уверенным.

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

В конце концов, свойство disposed обнулится, но, с моей точки зрения, это недетерминировано.

Основная причина, как намекает dbkk, заключается в том, что родительский контейнер ( ObjA с PropB ) сохраняет экземпляр _PropB в области видимости, несмотря на наличие Dispose() .

В общем случае нет необходимости устанавливать значение 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 - относитесь к нему как к этому вездесущему Существу, которое защищает и наблюдает за вами ... (склоняет голову, поднимает кулак к небу) .. .

Лично я часто явно устанавливаю для переменных значение null, когда заканчиваю с ними, как форму самодокументирования. Я не объявляю, использую, а затем устанавливаю значение null позже - я обнуляю сразу после того, как они больше не нужны. Я прямо говорю: «Я официально с тобой покончил ... уходи ...»

Обнуление необходимо в языке GC? Нет. Это полезно для GC? Может быть, да, может быть, нет, не знаю наверняка, по замыслу я действительно не могу это контролировать, и независимо от сегодняшнего ответа с той или иной версией будущие реализации GC могут изменить ответ вне моего контроля. Плюс, если / когда обнуление будет оптимизировано, это будет немного больше, чем модный комментарий, если хотите.

Я полагаю, что если это проясняет мои намерения для следующего бедного дурака, который пойдет по моим стопам, и если это «может» иногда потенциально помочь GC, то оно того стоит для меня. В основном это заставляет меня чувствовать себя опрятно и ясно, а Монго любит чувствовать себя опрятным и чистым. :)

Я смотрю на это так: существуют языки программирования, позволяющие людям дать другим людям представление о намерениях, а компилятору - запрос задания о том, что делать - компилятор преобразует этот запрос на другой язык (иногда несколько) для ЦП - ЦП могут дать понять, какой язык вы использовали, настройки вкладок, комментарии, стилистические акценты, имена переменных и т.д. Многие вещи, написанные в коде, не преобразуются в то, что потребляет ЦП в указанной нами последовательности. Наши C, C++, C#, Lisp, Babel, ассемблер или что-то еще является теорией, а не реальностью, написанной как техническое задание. То, что вы видите, - это не то, что вы получаете, да, даже на языке ассемблера.

Я понимаю, что «ненужные вещи» (например, пустые строки) «не что иное, как шум и беспорядок в коде». Это был я раньше в моей карьере; Я полностью понимаю это. На данном этапе я склоняюсь к тому, что делает код более понятным. Это не значит, что я добавляю в свои программы даже 50 строк «шума» - это несколько строк здесь или там.

Из любого правила есть исключения. В сценариях с энергозависимой памятью, статической памятью, условиями гонки, одиночными копиями, использованием «устаревших» данных и всем подобным гнилью все по-другому: вам НЕОБХОДИМО управлять своей собственной памятью, блокируя и аннулируя, как это возможно, потому что память не является частью Вселенная GC'd - надеюсь, все это понимают. В остальное время с языками GC это вопрос стиля, а не необходимости или гарантированного повышения производительности.

В конце концов, убедитесь, что вы понимаете, что подходит для GC, а что нет; блокировать, удалять и аннулировать соответствующим образом; воск на, воск прочь; вдох-выдох; и обо всем остальном я говорю: если тебе нравится, делай это. Ваш пробег может отличаться ... как и должно ...

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

Я считаю, что одноразовые фреймворки позволят выбросить ObjectDisposedException, что более значимо. По этой причине было бы лучше не возвращать их к нулю.

Стивен Клири очень хорошо объясняет в этом посте: Должен ли я установить для переменных значение NULL, чтобы облегчить сборку мусора?

Говорит:

Краткий ответ для нетерпеливых. Да, если переменная является статическим полем, или если вы пишете перечислимый метод (используя yield return) или асинхронный метод (используя async и await). В противном случае нет.

Это означает, что в обычных методах (неперечислимых и неасинхронных) вы не устанавливаете для локальных переменных, параметров метода или полей экземпляра значение NULL.

(Даже если вы реализуете IDisposable.Dispose, вам все равно не следует устанавливать для переменных значение null).

Важная вещь, которую мы должны учитывать, - это статические поля .

Статические поля всегда являются корневыми объектами , поэтому сборщик мусора всегда считает их «живыми» . Если статическое поле ссылается на объект, который больше не нужен, ему следует присвоить значение null, чтобы сборщик мусора считал его подходящим для сбора.

Установка для статических полей значения null бессмысленна, если весь процесс завершается. В этот момент вся куча будет собрана для сборки мусора, включая все корневые объекты.

Заключение:

Статические поля ; вот об этом. Все остальное - пустая трата времени .

Некоторые объекты предполагают .dispose() метод, который принудительно удаляет ресурс из памяти.

Также:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Нет, не обнуляйте объекты. Вы можете проверить https://web.archive.org/web/20160325050833/http://codebetter.com/karlseguin/2008/04/28/foundations-of-programming-pt-7-back-to-basics- memory / для получения дополнительной информации, но установка значения null ничего не сделает, кроме загрязнения вашего кода.

Единственный раз, когда вы должны установить для переменной значение null, - это когда переменная не выходит за пределы области видимости и вам больше не нужны связанные с ней данные. Иначе в этом нет необходимости.

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

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

В общем, не стоит беспокоиться. Пусть компилятор и сборщик мусора делают свою работу, а вы можете делать свою.

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

Если объект реализует IDisposable и хранится в поле, я думаю, что хорошо обнулить его, просто чтобы избежать использования удаленного объекта. Ошибки следующего вида могут быть болезненными:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

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

Скорее всего, ваш код не структурирован достаточно четко, если вы чувствуете необходимость в 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

Я считаю, что использование фигурных скобок без «заголовка» действительно помогает очистить код и сделать его более понятным.

Еще одна причина, по которой не следует устанавливать для объектов значение 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 обычно оптимизируется прочь присваивание нуля , так как биты конца кода вверх является одинаковым.

Взгляните также на эту статью: http://www.codeproject.com/KB/cs/idisposable.aspx

По большей части установка объекта на null не имеет никакого эффекта. Обязательно делать это только в том случае, если вы работаете с «большим объектом», размер которого превышает 84 КБ (например, растровые изображения).