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


187

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

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

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

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

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


4
+1 отличный вопрос. Кто-нибудь знает обстоятельства, при которых компилятор полностью оптимизирует назначение? то есть кто-нибудь смотрел на MSIL при других обстоятельствах и отмечал IL для установки объекта на ноль (или его отсутствие).
Тим Медора

Ответы:


73

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

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

Копаться в IDisposable

и это

Понимание IDisposable

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


6
Правило о том, что не следует устанавливать значение NULL, не является «жестким и быстрым» ... если объект помещается в кучу больших объектов (размер> 85 КБ), это поможет ГХ, если вы установите для объекта значение NULL, когда вы закончите. используй это.
Скотт Дорман

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

21
Весь этот бизнес «не преждевременно оптимизировать» звучит больше как «Предпочитаю медленный и не беспокойтесь, потому что процессоры становятся быстрее, а приложения CRUD в любом случае не нуждаются в скорости». Это может быть только я, хотя. :)
BobbyShaftoe

19
На самом деле это означает: «Сборщик мусора лучше управляет памятью, чем вы». Это может быть только я, хотя. :)
BobRodes

2
@BobbyShaftoe: Вероятно, так же неправильно говорить, что «преждевременная оптимизация - это плохо, всегда», так как это означает переход к противоположной крайности «звучит больше как« предпочитайте медленный »». Ни один разумный программист не скажет. Речь идет о нюансах и умном подходе к оптимизации. Лично я бы беспокоился о ясности кода и ЭТОЙ АКТУАЛЬНО ТЕСТИРОВАНИИ, поскольку лично я видел, как многие люди (в том числе и я, когда я был моложе) тратят слишком много времени на создание «идеального» алгоритма, только для того, чтобы сэкономить 0,1 мс. в 100 000 итераций все, пока читаемость была полностью снята.
Брент Риттенхаус

36

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

например

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

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


Это интересный момент. Я всегда думал, что объекты не выходят за рамки, пока не завершится метод, в котором они определены. Если, конечно, объект не находится в пределах блока Using или явно имеет значение Nothing или null.
Гуру Джош

1
Предпочтительный способ гарантировать, что они останутся в живых, это использовать. GC.KeepAlive(someType); См. Ericlippert.com/2013/06/10/construction-destruction
NotMe

14

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


1
Приятное и подробное объяснение памяти в общедоступной ссылке
user2323308

Ссылка не работает. Без связанного контента этот ответ скорее бесполезен и должен быть удален.
Imre Pühvel


7

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

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

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

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


8
Хорошо, удаленный объект должен генерировать ObjectDisposedException, если он уже был удален. Насколько мне известно, для этого требуется повсеместный шаблонный код, но опять же, Disposed в любом случае является плохо продуманной парадигмой.
nicodemus13

3
Ctrl + F для .Dispose(). Если вы найдете его, вы не используете IDisposable правильно. Единственное использование для одноразового предмета должно быть в пределах блока использования. А после использования блока у вас даже нет доступа к нему myField. А в блоке using установка значения nullне обязательна, блок using выберет объект для вас.
Суамер

7

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

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


Однажды я попытался использовать пользовательские локальные области видимости (в основном это смартта $$). Компания взорвалась.
Суамер

Еще одно замечание: это потому, что компилятор c # найдет переменные локальной области, которые реализуют IDisposable, и вызовет .Dispose (MOST Of the time), когда закончится их область действия. Однако ... SQL-соединения - это одно большое время, когда .Dispose () никогда не оптимизируется. Есть некоторые типы, которые требуют явного внимания, поэтому я лично всегда делаю вещи явно, чтобы меня не укусили.
Суамер

5

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


2
Это правда, но это также означает, что вы, вероятно, должны провести рефакторинг своего кода. Я не думаю, что мне когда-либо нужно было объявлять переменную вне ее предполагаемой области видимости.
Карл Сегуин

2
Если понимать, что «переменная» включает в себя поля объекта, тогда этот ответ имеет большой смысл. В случае, когда «переменная» означает только «локальная переменная» (метода), тогда мы, вероятно, говорим здесь о нишевых случаях (например, метод, который выполняется намного дольше, чем обычно).
stakx - больше не вносит свой вклад

5

В общем случае нет необходимости устанавливать значение NULL. Но предположим, что у вас есть функция сброса в вашем классе.

Тогда вы можете это сделать, потому что вы не хотите вызывать dispose дважды, поскольку некоторые из Dispose могут быть реализованы неправильно и вызывать исключение System.ObjectDisposed.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }

Лучше всего просто отслеживать это с отдельным флагом, может быть.
Thulani Chivandikwa

3

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

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

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

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

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

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

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

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

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

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

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


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

1

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

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

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


1

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

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


1

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

Говорит:

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

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

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

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

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

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

Вывод:

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


0

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

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

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

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

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

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

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

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


0

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

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


-1

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


11
Нет, это не так; Dispose () не собирает объект - он используется для выполнения детерминированной очистки, обычно освобождая неуправляемые ресурсы.
Марк Гравелл

1
Учитывая, что детерминизм относится только к управляемым ресурсам, а не к неуправляемым (т.
Е. К
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.