Вам нужно избавиться от объектов и установить их на ноль?
Вам нужно избавиться от объектов и установить для них значение null, или сборщик мусора очистит их, когда они выйдут из области видимости?
13 ответов
Объекты будут очищены, когда они больше не используются и когда сборщик мусора сочтет нужным. Иногда вам может понадобиться установить объект на 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();
}
}
Объекты никогда не выходят из области видимости в 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# объект ссылки
Джон Скит сравнил ссылки на объекты в Ja va с кусочками строк, которые прикреплены к всплывающей подсказке, которая является объектом. Та же аналогия применима к ссылкам на объекты C#. Они просто указывают на местоположение кучи, содержащей объект. Таким образом, установка его в null не оказывает непосредственного влияния на время жизни объекта, баллон продолжает существовать, пока GC не "вытолкнет" его.
Продолжая аналогию с воздушным шаром, представляется логичным, что, если к воздушному шару не прикреплены нити, он может быть уничтожен. Фактически именно так работают объекты с подсчетом ссылок в неуправляемых языках. За исключением того, что этот подход не работает для циклических ссылок очень хорошо. Представьте себе два воздушных шарика, которые соединены друг с другом цепочкой, но ни у одного из них нет цепочки ни к чему другому. По простым правилам подсчета ссылок они оба продолжают существовать, даже если вся группа воздушных шаров "осиротела".
.NET объекты очень похожи на гелиевые шарики под крышей. Когда крыша открывается (GC бежит) - неиспользованные воздушные шары всплывают, хотя могут быть группы воздушных шаров, которые связаны друг с другом.
.NET GC использует комбинацию поколений GC и меток и разверток. Подход, основанный на поколениях, предполагает предпочтение среды выполнения проверять объекты, которые были выделены в последнее время, поскольку они с большей вероятностью не используются, а разметка и развертка включают в себя время выполнения, проходящее весь граф объектов и определяющее, существуют ли группы объектов, которые не используются. Это адекватно решает проблему циклической зависимости.
Кроме того, .NET GC работает в другом потоке (так называемом потоке финализатора), так как у него есть немало дел, и выполнение этого в основном потоке прерывает вашу программу.
Как уже говорили другие, вы определенно хотите позвонить 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-компилятор может распознавать, когда ссылки на объекты больше не разыменовываются, даже если они все еще укоренены.
Вам никогда не нужно устанавливать нулевые объекты в C#. Компилятор и среда выполнения позаботятся о том, чтобы выяснить, когда они больше не находятся в области видимости.
Да, вы должны избавляться от объектов, которые реализуют IDisposable.
Если объект реализует IDisposable
тогда да, вы должны избавиться от него. Объект может висеть на собственных ресурсах (файловых дескрипторах, объектах ОС), которые иначе не могут быть освобождены немедленно. Это может привести к нехватке ресурсов, проблемам с блокировкой файлов и другим незначительным ошибкам, которых в противном случае можно было бы избежать.
Смотрите также Реализация метода удаления в MSDN.
Я согласен с общим ответом здесь: да, вы должны утилизировать, и нет, вы вообще не должны устанавливать переменную в null... но я хотел бы отметить, что утилизация - это НЕ в первую очередь управление памятью. Да, это может помочь (и иногда помогает) с управлением памятью, но его основная цель - дать вам детерминистическое освобождение дефицитных ресурсов.
Например, если вы открываете аппаратный порт (например, последовательный), сокет TCP/IP, файл (в режиме исключительного доступа) или даже соединение с базой данных, вы теперь запретили любому другому коду использовать эти элементы, пока они не будут освобождены. Утилита обычно освобождает эти элементы (наряду с GDI и другими дескрипторами "os" и т. Д., Которые доступны тысячами, но в целом все еще ограничены). Если вы не вызываете dipose для объекта-владельца и не освобождаете эти ресурсы явным образом, попробуйте снова открыть тот же ресурс в будущем (или это сделает другая программа), эта попытка открытия не удастся, поскольку в вашем нераспределенном, невыбранном объекте по-прежнему открыт элемент, Конечно, когда GC собирает элемент (если шаблон Dispose был реализован правильно), ресурс освобождается... но вы не знаете, когда это произойдет, поэтому вы не знаете, когда будет безопасно повторно откройте этот ресурс. Это основная проблема, с которой Dispose работает. Конечно, освобождение этих дескрипторов также часто освобождает память, и никогда не освобождая их, возможно, никогда не освободим эту память... отсюда и все разговоры о утечках памяти или задержках очистки памяти.
Я видел реальные примеры этого вызывающего проблемы. Например, я видел веб-приложения ASP.Net, которые в конечном итоге не могут подключиться к базе данных (хотя и в течение коротких периодов времени или до тех пор, пока процесс веб-сервера не будет перезапущен), потому что "пул соединений сервера sql заполнен"... т.е. так много соединений было создано и явно не освобождено за столь короткий промежуток времени, что новые соединения не могут быть созданы, и многие из соединений в пуле, хотя и не активны, по-прежнему ссылаются на неназванные и несобранные объекты и поэтому могут ' не может быть повторно использован. Правильное размещение соединений с базой данных, где это необходимо, предотвращает возникновение этой проблемы (по крайней мере, если у вас нет очень высокого одновременного доступа).
Если они реализуют интерфейс IDisposable, вы должны утилизировать их. Сборщик мусора позаботится обо всем остальном.
РЕДАКТИРОВАТЬ: лучше всего использовать using
Команда при работе с одноразовыми предметами:
using(var con = new SqlConnection("..")){ ...
Когда объект реализует 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
Всегда звоните утилизировать. Это не стоит риска. К крупным управляемым корпоративным приложениям следует относиться с уважением. Никакие предположения не могут быть сделаны, иначе он вернется, чтобы укусить вас.
Не слушай Леппи.
Многие объекты на самом деле не реализуют IDisposable, поэтому вам не нужно беспокоиться о них. Если они действительно выйдут из области видимости, они будут автоматически освобождены. Также я никогда не сталкивался с ситуацией, когда мне приходилось устанавливать что-либо на ноль.
Одна вещь, которая может случиться, состоит в том, что много объектов можно держать открытыми. Это может значительно увеличить использование памяти вашего приложения. Иногда трудно понять, действительно ли это утечка памяти, или ваше приложение просто делает много вещей.
Инструменты профиля памяти могут помочь с такими вещами, но это может быть сложно.
Кроме того всегда отписывайтесь от событий, которые не нужны. Также будьте осторожны с привязкой WPF и элементами управления. Не обычная ситуация, но я столкнулся с ситуацией, когда у меня был элемент управления WPF, который был привязан к базовому объекту. Базовый объект был большим и занимал большой объем памяти. Элемент управления WPF заменялся новым экземпляром, а старый почему-то все еще зависал. Это вызвало большую утечку памяти.
На заднем плане код был написан плохо, но дело в том, что вы хотите убедиться, что вещи, которые не используются, выходят за рамки. Это заняло много времени, чтобы найти с помощью профилировщика памяти, так как трудно понять, что в памяти является допустимым, а что не должно быть там.
Обычно нет необходимости устанавливать поля в null. Однако я бы всегда рекомендовал избавляться от неуправляемых ресурсов.
По своему опыту я бы также посоветовал вам сделать следующее:
- Отписаться от событий, если они вам больше не нужны.
- Установите любое поле, содержащее делегат или выражение, в null, если оно больше не нужно.
Я столкнулся с некоторыми очень трудными, чтобы найти проблемы, которые были прямым результатом не следования совету выше.
Хорошее место для этого - Dispose(), но обычно лучше.
В общем, если существует ссылка на объект, сборщик мусора (GC) может занять пару поколений дольше, чтобы выяснить, что объект больше не используется. Все время объект остается в памяти.
Это не может быть проблемой, пока вы не обнаружите, что ваше приложение использует намного больше памяти, чем вы ожидаете. Когда это произойдет, подключите профилировщик памяти, чтобы увидеть, какие объекты не очищаются. Задание полей, ссылающихся на другие объекты как нулевые, и очистка коллекций при утилизации может действительно помочь GC выяснить, какие объекты он может удалить из памяти. GC будет восстанавливать использованную память быстрее, делая ваше приложение намного менее требовательным к памяти и быстрее.
Я тоже должен ответить. JIT генерирует таблицы вместе с кодом из статического анализа использования переменных. Эти записи таблицы являются "корнями GC" в текущем кадре стека. По мере продвижения указателя инструкций эти записи в таблице становятся недействительными и поэтому готовы к сборке мусора. Поэтому: если это переменная в области видимости, вам не нужно устанавливать ее в null - GC будет собирать объект. Если это член или статическая переменная, вы должны установить его на нуль
Немного поздно для вечеринки, но есть один сценарий, который, я думаю, здесь не упоминался: если класс A реализует IDisposable и предоставляет общедоступные свойства, которые также являются объектами IDisposable, то я думаю, что это хорошая практика для класса A не только для удаления одноразовых объектов, созданных в методе Dispose, но также и для установки для них значения null. Причина этого в том, что удаление объекта и предоставление ему возможности пройти сборщик мусора (поскольку на него больше нет ссылок) ни в коем случае не одно и то же, хотя это определенно ошибка, если это происходит. Если клиент класса A действительно размещает свой объект типа ClassA, объект все еще существует. Если затем клиент попытается получить доступ к одному из этих общедоступных свойств (которые теперь также удалены), результаты могут быть совершенно неожиданными. Если они были аннулированы, а также утилизированы,
В этом эпизоде .NET Rocks есть хорошая дискуссия на эту тему (а также история создания шаблона Dispose)!