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

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

Ответ 1

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

Ответ 2

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

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

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

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

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

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

EDIT В области переменных. Крейг спросил, влияет ли область видимости переменной на время жизни объекта. Чтобы правильно объяснить этот аспект CLR, мне нужно будет объяснить несколько концепций из С++ и С#.

Область действительной переменной

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

В С++ это совершенно легально:

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

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

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, фактически определен на уровне функции. Эффективно это означает, что С# обладает только областью уровня класса и уровня в зависимости от времени жизни переменной.

.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

Объем и время жизни С++

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

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

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

С# Object Lifetime

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

MyClass stackObj;

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

MyClass stackObj = new MyClass();

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

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

Ссылки на С#

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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

EDIT: лучше всего использовать команду using при работе с одноразовыми элементами:

using(var con = new SqlConnection("..")){ ...

Ответ 8

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

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

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

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

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

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

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

Ответ 9

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

Не слушайте leppie.

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

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

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

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

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

Ответ 10

Когда объект реализует 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

Ответ 11

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