Вам нужно избавиться от объектов и установить их на ноль? - PullRequest
285 голосов
/ 28 мая 2010

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

Ответы [ 12 ]

223 голосов
/ 28 мая 2010

Объекты будут очищены, когда они больше не используются и когда сборщик мусора сочтет нужным. Иногда вам может потребоваться установить объект на 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();
    }
}
129 голосов
/ 28 мая 2010

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

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

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

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

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 работает в другом потоке (так называемом потоке финализатора), так как у него довольно много работы, и выполнение этого в основном потоке прерывает вашу программу.

17 голосов
/ 28 мая 2010

Как уже говорили другие, вы определенно хотите вызвать 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-компилятор может распознавать, когда ссылки на объекты больше не разыменовываются, даже если они все еще укоренены.

13 голосов
/ 28 мая 2010

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

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

11 голосов
/ 02 июня 2010

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

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

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

11 голосов
/ 28 мая 2010

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

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

9 голосов
/ 28 мая 2010

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

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

using(var con = new SqlConnection("..")){ ...
4 голосов
/ 02 июня 2010

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

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

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

Пример:

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
4 голосов
/ 28 мая 2010

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

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

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

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

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

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

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

3 голосов
/ 02 июня 2010

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

Не слушай леппи.

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

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

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

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

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

...