Вам нужно избавиться от объектов и установить их на нуль?

310

Вам нужно избавиться от объектов и установить для них значение NULL, или сборщик мусора очистит их, когда они выйдут из области видимости?

CJ7
источник
4
Похоже, что существует консенсус, что вам не нужно устанавливать объект в null, но нужно ли вам выполнять Dispose ()?
CJ7
3
как сказал Джефф: codinghorror.com/blog/2009/01/…
tanathos
9
Мой совет всегда распоряжаться, если объект реализует IDisposable. Используйте блок использования каждый раз. Не делайте предположений, не оставляйте это на волю случая. Вы не должны устанавливать все в ноль, хотя. Объект только что вышел из области видимости.
Питер
11
@peter: Не используйте блоки «использование» с прокси-клиентами WCF: msdn.microsoft.com/en-us/library/aa355056.aspx
nlawalker
9
ОДНАКО, ВЫ МОЖЕТЕ хотеть установить некоторые ссылки на null внутри вашего Dispose()метода! Это небольшая вариация в этом вопросе, но важная, потому что удаляемый объект не может знать, выходит ли он из области видимости (вызов не Dispose()является гарантией). Больше здесь: stackoverflow.com/questions/6757048/…
Кевин П. Райс

Ответы:

239

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

Что касается утилизации предметов, я согласен с @Andre. Если объект IDisposableявляется хорошим, рекомендуется утилизировать его, когда он вам больше не нужен, особенно если объект использует неуправляемые ресурсы. Неиспользование неуправляемых ресурсов приведет к утечкам памяти .

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

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

Что функционально эквивалентно:

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}
Зак Джонсон
источник
4
Если obj является ссылочным типом, тогда блок finally эквивалентен:if (obj != null) ((IDisposable)obj).Dispose();
Рэнди поддерживает Монику
1
@ Tuzo: Спасибо! Отредактировано, чтобы отразить это.
Зак Джонсон
2
Одно замечание по поводу IDisposable. Если объект не удастся удалить, он, как правило, не вызовет утечку памяти ни у одного хорошо спроектированного класса. При работе с неуправляемыми ресурсами в C # у вас должен быть финализатор, который по-прежнему будет освобождать неуправляемые ресурсы. Это означает, что вместо освобождения ресурсов, когда это должно быть сделано, оно будет отложено до того момента, когда сборщик мусора завершит работу над управляемым объектом. Это все еще может вызвать много других проблем (таких как невыпущенные блокировки). Вы должны утилизировать IDisposableхотя!
Aidiakapi
@RandyLevy У вас есть ссылка на это? Спасибо
Basic
Но мой вопрос заключается в том, что Dispose () должен реализовать какую-либо логику? Это должно что-то делать? Или внутренне, когда Dispose () называется GC сигналов, что хорошо идти? Я проверил исходный код для TextWriter, например, и Dispose не имеет реализации.
Михаил Георгеску
137

Объекты никогда не выходят из области видимости в C #, как в C ++. Они обрабатываются сборщиком мусора автоматически, когда они больше не используются. Это более сложный подход, чем C ++, где область действия переменной полностью детерминирована. Сборщик мусора CLR активно просматривает все созданные объекты и работает, если они используются.

Объект может выйти «из области видимости» в одной функции, но если его значение будет возвращено, то GC посмотрит, удерживает ли вызывающая функция возвращаемое значение.

Установка ссылок на объекты не nullявляется необходимой, поскольку сборка мусора работает, определяя, на какие объекты ссылаются другие объекты.

На практике вам не нужно беспокоиться о разрушении, оно просто работает и это здорово :)

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

using (var ms = new MemoryStream()) {
  //...
}

РЕДАКТИРОВАТЬ На переменной области. Крейг спросил, влияет ли переменная область действия на время жизни объекта. Чтобы правильно объяснить этот аспект CLR, мне нужно объяснить несколько понятий из C ++ и C #.

Фактическая область видимости переменной

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

В C ++ это совершенно законно:

int iVal = 8;
//iVal == 8
if (iVal == 8){
    int iVal = 5;
    //iVal == 5
}
//iVal == 8

Однако в C # вы получаете ошибку компилятора:

int iVal = 8;
if(iVal == 8) {
    int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}

Это имеет смысл, если вы посмотрите на сгенерированный MSIL - все переменные, используемые функцией, определены в начале функции. Посмотрите на эту функцию:

public static void Scope() {
    int iVal = 8;
    if(iVal == 8) {
        int iVal2 = 5;
    }
}

Ниже сгенерированный IL. Обратите внимание, что iVal2, который определен внутри блока if, фактически определен на уровне функций. Фактически это означает, что C # имеет только область действия класса и уровня функций, что касается времени жизни переменной.

.method public hidebysig static void  Scope() cil managed
{
  // Code size       19 (0x13)
  .maxstack  2
  .locals init ([0] int32 iVal,
           [1] int32 iVal2,
           [2] bool CS$4$0000)

//Function IL - omitted
} // end of method Test2::Scope

Область действия C ++ и время жизни объекта

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

if (true) {
  MyClass stackObj; //created on the stack
  MyClass heapObj = new MyClass(); //created on the heap
  obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives

Когда объекты C ++ создаются в куче, они должны быть явно уничтожены, иначе это утечка памяти. Нет такой проблемы с переменными стека, хотя.

Время жизни объекта C #

В CLR объекты (т.е. ссылочные типы) всегда создаются в управляемой куче. Это дополнительно подкрепляется синтаксисом создания объекта. Рассмотрим этот фрагмент кода.

MyClass stackObj;

В C ++ это создаст экземпляр MyClassв стеке и вызовет его конструктор по умолчанию. В C # это создаст ссылку на класс MyClass, который ни на что не указывает. Единственный способ создать экземпляр класса - использовать newоператор:

MyClass stackObj = new MyClass();

В некотором смысле, объекты C # во многом похожи на объекты, которые создаются с использованием newсинтаксиса в C ++ - они создаются в куче, но в отличие от объектов C ++, они управляются средой выполнения, поэтому вам не нужно беспокоиться об их уничтожении.

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

C # объект ссылки

Джон Скит сравнил ссылки на объекты в Java с кусочками строк, которые прикреплены к всплывающей подсказке, которая является объектом. Та же аналогия применима к ссылкам на объекты C #. Они просто указывают на местоположение кучи, содержащей объект. Таким образом, установка его в null не оказывает непосредственного влияния на время жизни объекта, баллон продолжает существовать до тех пор, пока GC не «вытолкнет» его.

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

.NET объекты очень похожи на гелиевые шарики под крышей. Когда крыша открывается (GC бежит) - неиспользованные воздушные шары всплывают, хотя могут быть группы воздушных шаров, которые связаны друг с другом.

.NET GC использует комбинацию поколений GC и меток и разверток. Подход с использованием поколений включает в себя среду выполнения, предпочитающую проверять объекты, которые были выделены в последнее время, поскольку они с большей вероятностью будут неиспользованными, а разметка и развертка - во время выполнения, просматривая весь граф объектов и выясняя, есть ли группы объектов, которые не используются. Это адекватно решает проблему круговой зависимости.

Кроме того, .NET GC работает в другом потоке (так называемом потоке финализатора), так как у него есть немало дел, и выполнение этого в основном потоке прерывает вашу программу.

Игорь Зевака
источник
1
@Igor: Под выходом из области видимости я подразумеваю, что ссылка на объект находится вне контекста и не может быть указана в текущей области видимости. Конечно, это все еще происходит в C #.
CJ7
@Craig Johnston, не путайте область видимости переменных, используемую компилятором, с временем жизни переменных, которое определяется временем выполнения - они разные. Локальная переменная не может быть «живой», даже если она все еще находится в области видимости.
Рэнди поддерживает Монику
1
@Craig Johnston: см. Blogs.msdn.com/b/ericgu/archive/2004/07/23/192842.aspx : «нет никакой гарантии, что локальная переменная останется действительной до конца области действия, если это не так время выполнения свободно анализировать имеющийся у него код и определять, что больше не используется переменной за пределами определенной точки, и, следовательно, не поддерживать эту переменную живущей за пределами этой точки (т.е. не обрабатывать ее как корень для целей GC). "
Рэнди поддерживает Монику
1
@ Tuzo: правда. Вот для чего нужен GC.KeepAlive.
Стивен Судит
1
@ Крейг Джонстон: нет и да. Нет, потому что .NET Runtime управляет этим для вас и делает хорошую работу. Да, потому что работа программиста состоит не в написании кода, который (просто) компилируется, а в написании кода, который выполняется . Иногда полезно знать, что среда выполнения делает под прикрытием (например, устранение неполадок). Можно утверждать, что это тот тип знаний, который помогает отделить хороших программистов от великих программистов.
Рэнди поддерживает Монику
18

Как уже говорили другие, вы определенно хотите позвонить, Disposeесли класс реализует IDisposable. Я занимаю довольно жесткую позицию по этому вопросу. Некоторые могут утверждать , что вызов Disposeна DataSet, к примеру, не имеет смысла , потому что они разобрали его и увидел , что он ничего содержательного не делать. Но я думаю, что в этом аргументе есть множество ошибок.

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

Теперь о том, следует ли вам установить ссылку на null. Ответ - нет. Позвольте мне проиллюстрировать мою точку зрения следующим кодом.

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

Итак, когда вы думаете, объект, на который ссылается, aимеет право на коллекцию? Если вы сказали после звонка, a = nullто вы не правы. Если вы сказали, что после завершения Mainметода вы также ошибаетесь. Правильный ответ: он может быть забран во время звонка DoSomething. Это верно. Он имеет право до того, как будет установлена ​​ссылка, nullи, возможно, даже до завершения вызова DoSomething. Это связано с тем, что JIT-компилятор может распознавать, когда ссылки на объекты больше не разыменовываются, даже если они все еще укоренены.

Брайан Гидеон
источник
3
Что делать, если aэто частное поле члена в классе? Если aне установлено в ноль, GC не может знать, aбудет ли снова использоваться в каком-либо методе, верно? Таким образом a, не будет собираться, пока не будет собран весь содержащий класс. Нет?
Кевин П. Райс
4
@ Кевин: Верно. Если бы он aбыл членом класса, а класс, содержащий его, aбыл все еще укоренен и использовался, он бы тоже зависал. Это один из сценариев, где его установка nullможет быть полезной.
Брайан Гидеон
1
Ваша точка зрения связана с причиной, по которой Disposeэто важно - невозможно вызвать Dispose(или любой другой не встроенный метод) объект без корневой ссылки на него; вызов Disposeпосле того, как объект был выполнен с использованием объекта, будет гарантировать, что корневая ссылка будет продолжать существовать в течение всего времени последнего действия над ним. Отказ от всех ссылок на объект без вызова Disposeможет по иронии судьбы привести к тому, что ресурсы объекта иногда высвобождаются слишком рано .
суперкат
Этот пример и объяснение не кажутся окончательными при сложном предложении никогда не устанавливать ссылки на нуль. Я имею в виду, что, за исключением комментария Кевина, ссылка, установленная на нуль после его удаления, выглядит довольно доброкачественно , так в чем же вред? Я что-то упускаю?
Датампсон
13

Вам никогда не нужно устанавливать нулевые объекты в C #. Компилятор и среда выполнения позаботятся о том, чтобы выяснить, когда они больше не находятся в области видимости.

Да, вы должны избавляться от объектов, которые реализуют IDisposable.

EMP
источник
2
Если у вас есть долгоживущая (или даже статическая) ссылка на большой объект, вы wantможете обнулить его, как только закончите, чтобы его можно было бесплатно восстанавливать.
Стивен Судит
12
Если вы когда-нибудь «покончили с этим», это не должно быть статичным. Если он не статичен, а «долговечен», то он все равно должен выйти из области видимости вскоре после того, как вы покончили с этим. Необходимость установить ссылки на нуль указывает на проблему со структурой кода.
EMP
У вас может быть статический предмет, с которым вы закончите. Обратите внимание: статический ресурс, который читается с диска в удобном для пользователя формате, а затем анализируется в формате, подходящем для использования программой. Вы можете получить личную копию необработанных данных, которая больше не нужна. (Пример из реальной жизни: синтаксический анализ - это
двухпроцессная
1
Тогда он не должен хранить какие-либо необработанные данные в статическом поле, если он используется только временно. Конечно, вы можете сделать это, просто это не очень хорошая практика именно по этой причине: вам придется управлять его временем жизни вручную.
EMP
2
Вы избегаете этого, сохраняя необработанные данные в локальной переменной в методе, который их обрабатывает. Метод возвращает обработанные данные, которые вы сохраняете, но локальный для необработанных данных выходит из области действия при выходе из метода и автоматически GCed.
EMP
11

Я согласен с общим ответом здесь: да, вы должны утилизировать, и нет, вы вообще не должны устанавливать переменную в null ... но я хотел бы отметить, что утилизация - это НЕ в первую очередь управление памятью. Да, это может помочь (и иногда помогает) с управлением памятью, но его основная цель - дать вам детерминистическое освобождение дефицитных ресурсов.

Например, если вы открываете аппаратный порт (например, последовательный), сокет TCP / IP, файл (в режиме эксклюзивного доступа) или даже соединение с базой данных, вы теперь запретили любому другому коду использовать эти элементы, пока они не будут освобождены. Утилита обычно освобождает эти элементы (вместе с GDI и другими дескрипторами «os» и т. Д., Которые доступны тысячами, но в целом все еще ограничены). Если вы не вызываете dipose для объекта-владельца и не освобождаете эти ресурсы явным образом, попробуйте снова открыть тот же ресурс в будущем (или это делает другая программа), эта попытка открытия не удастся, поскольку в вашем нераспределенном, невыбранном объекте по-прежнему открыт элемент , Конечно, когда GC собирает элемент (если шаблон Dispose был реализован правильно), ресурс будет освобожден ... но вы не знаете, когда это произойдет, поэтому вы не Не знаю, когда будет безопасно снова открыть этот ресурс. Это основная проблема, с которой Dispose работает. Конечно, освобождение этих дескрипторов также часто освобождает память, и никогда не освобождая их, возможно, никогда не освободим эту память ... отсюда и все разговоры о утечках памяти или задержках очистки памяти.

Я видел реальные примеры этого вызывающего проблемы. Например, я видел веб-приложения ASP.Net, которые в конечном итоге не могут подключиться к базе данных (хотя и в течение коротких периодов времени или до тех пор, пока процесс веб-сервера не будет перезапущен), поскольку «пул соединений сервера sql заполнен» ... т.е. так много соединений было создано и явно не освобождено за столь короткий промежуток времени, что никакие новые соединения не могут быть созданы, и многие из соединений в пуле, хотя и не активны, по-прежнему ссылаются на необнаруженные и несобранные объекты и поэтому могут ' не может быть повторно использован. Правильное размещение соединений с базой данных, где это необходимо, гарантирует, что эта проблема не произойдет (по крайней мере, если у вас нет очень высокого одновременного доступа).

Yort
источник
11

Если объект реализует IDisposable, то да, вы должны распоряжаться им. Объект может висеть на собственных ресурсах (файловых дескрипторах, объектах ОС), которые иначе не могут быть освобождены немедленно. Это может привести к нехватке ресурсов, проблемам с блокировкой файлов и другим незначительным ошибкам, которых в противном случае можно было бы избежать.

Смотрите также Реализация метода удаления в MSDN.

Крис Шмих
источник
Но не будет ли сборщик мусора вызывать Dispose ()? Если это так, зачем вам это звонить?
CJ7
Если вы не позвоните прямо, нет гарантии, что Disposeон будет вызван. Кроме того, если ваш объект удерживает дефицитный ресурс или блокирует какой-либо ресурс (например, файл), то вы захотите освободить его как можно скорее. Ожидание, пока GC сделает это, неоптимально.
Крис Шмих
12
GC никогда не вызовет Dispose (). GC может вызвать финализатор, который по соглашению должен очистить ресурсы.
Адриан
@adrianm: Не mightзвони, а willзвони.
Леппи
2
@leppie: финализаторы не являются детерминированными и могут не вызываться (например, когда домен приложения выгружен). Если вам нужна детерминированная финализация, вы должны реализовать то, что я считаю критическим обработчиком. У CLR есть специальная обработка этих объектов, чтобы гарантировать, что они завершены (например, он предшествует коду финализации для обработки
нехватки
9

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

РЕДАКТИРОВАТЬ: лучше всего использовать usingкоманду при работе с одноразовыми предметами:

using(var con = new SqlConnection("..")){ ...
Andre
источник
5

Когда объект реализуется, IDisposableвы должны вызывать Dispose(или Close, в некоторых случаях, это вызовет Dispose для вас).

Обычно вам не нужно устанавливать объекты null, потому что GC будет знать, что объект больше не будет использоваться.

Есть одно исключение, когда я устанавливаю объекты в null. Когда я получаю много объектов (из базы данных), над которыми мне нужно работать, и сохраняю их в коллекции (или массиве). Когда «работа» завершена, я устанавливаю объект на null, потому что GC не знает, что я закончил работу с ним.

Пример:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called
GvS
источник
4

Обычно нет необходимости устанавливать поля в null. Тем не менее, я бы всегда рекомендовал избавляться от неуправляемых ресурсов.

По своему опыту я бы также посоветовал вам сделать следующее:

  • Отписаться от событий, если они вам больше не нужны.
  • Установите любое поле, содержащее делегат или выражение, в null, если оно больше не требуется.

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

Хорошее место для этого - Dispose (), но обычно лучше.

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

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

Марникс ван Вален
источник
1
Что вы имеете в виду под «событиями и делегатами» - что следует «очистить» от них?
CJ7
@Craig - я отредактировал свой ответ. Надеюсь, это прояснит это немного.
Марникс ван Вален
3

Всегда звоните утилизировать. Это не стоит риска. К крупным управляемым корпоративным приложениям следует относиться с уважением. Никакие предположения не могут быть сделаны, иначе он вернется, чтобы укусить вас.

Не слушай про Леппи.

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

Одна вещь, которая может случиться, состоит в том, что много объектов можно держать открытыми. Это может значительно увеличить использование памяти вашего приложения. Иногда трудно понять, действительно ли это утечка памяти, или ваше приложение просто делает много вещей.

Инструменты профиля памяти могут помочь с такими вещами, но это может быть сложно.

Кроме того всегда отписывайтесь от событий, которые не нужны. Также будьте осторожны с привязкой WPF и элементами управления. Не обычная ситуация, но я столкнулся с ситуацией, когда у меня был элемент управления WPF, который был привязан к базовому объекту. Основной объект был большим и занимал большой объем памяти. Элемент управления WPF заменялся новым экземпляром, а старый почему-то все еще зависал. Это вызвало большую утечку памяти.

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

Питер
источник
2

Я тоже должен ответить. JIT генерирует таблицы вместе с кодом из статического анализа использования переменных. Эти записи таблицы являются «корнями GC» в текущем кадре стека. По мере продвижения указателя инструкций эти записи таблицы становятся недействительными и поэтому готовы к сборке мусора. Поэтому: если это переменная в области видимости, вам не нужно устанавливать ее в null - GC будет собирать объект. Если это член или статическая переменная, вы должны установить его на нуль

Hui
источник