Необходимо ли избавиться от объектов и установить ли их в NULL?

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

299
задан FishBasketGordo 29 July 2011 в 01:35
поделиться

11 ответов

Объекты будут очищены, когда они больше не будут использоваться и когда сборщик мусора сочтет нужным. Иногда вам может потребоваться установить для объекта значение 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();
    }
}
236
ответ дан 23 November 2019 в 01:29
поделиться

В этом выпуске .NET Rocks есть хорошее обсуждение этой темы (наряду с историей, лежащей в основе шаблона Dispose)!

http://www.dotnetrocks.com/default.aspx?showNum=10

0
ответ дан 16 September 2019 в 23:24
поделиться

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

Да, вам следует избавляться от объектов, реализующих IDisposable.

13
ответ дан 23 November 2019 в 01:29
поделиться

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

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

using(var con = new SqlConnection("..")){ ...
9
ответ дан 23 November 2019 в 01:29
поделиться

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

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

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

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

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

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

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

4
ответ дан 23 November 2019 в 01:29
поделиться

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

17
ответ дан 23 November 2019 в 01:29
поделиться

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

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

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

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

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

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

EDIT Об области видимости переменных. Крейг спросил, влияет ли область видимости переменных на время жизни объекта. Чтобы правильно объяснить этот аспект 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#

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

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

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

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

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

133
ответ дан 23 November 2019 в 01:29
поделиться

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

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

11
ответ дан 23 November 2019 в 01:29
поделиться

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

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

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

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

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

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

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

3
ответ дан 23 November 2019 в 01:29
поделиться

Когда объект реализует 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
5
ответ дан 23 November 2019 в 01:29
поделиться

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

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

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

11
ответ дан 23 November 2019 в 01:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: