Установка объекта в null vs Dispose()


93

Я восхищен тем, как работает CLR и GC (я работаю над расширением моих знаний об этом, читая CLR через C#, книги/посты Jon Skeet и многое другое).

Во всяком случае, в чем разница между словами:

MyClass myclass = new MyClass(); 
myclass = null; 

Или, сделав MyClass реализовать IDisposable и деструктора и вызова Dispose()?

Кроме того, если у меня есть кодовый блок с использованием оператора (например, ниже), если я пройду через код и выйду из блока использования, будет ли объект удален или когда произойдет сборка мусора? Что произойдет, если я вызову Dispose() в блоке using anyay?

using (MyDisposableObj mydispobj = new MyDisposableObj()) 
{ 

} 

Потолочные классы (например, BinaryWriter) имеют метод Finalize? Почему я хочу использовать это?

185

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

Dispose, вывоз мусора и финализации

Когда вы пишете using заявление, это просто синтаксический сахар для TRY/наконец, блок, так что Dispose называется, даже если код в теле using заявления бросает исключение. Он не означает, что объект является мусором, собранным в конце блока.

Утилизация около неуправляемые ресурсы (ресурсы без памяти). Это могут быть интерфейсы UI, сетевые подключения, дескрипторы файлов и т. Д. Это ограниченные ресурсы, поэтому вы обычно хотите их освободить, как только сможете. Вы должны реализовать IDisposable, когда ваш тип «владеет» неуправляемым ресурсом, либо напрямую (обычно через IntPtr), либо косвенно (например, через Stream, SqlConnection и т. Д.).

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

Закрутка завершение. Сборщик мусора хранит список объектов, которые уже недоступны, но которые имеют финализатор (написанный как ~Foo() в C#, несколько смутно - они не похожи на деструкторы C++). Он запускает финализаторы на этих объектах, на случай, если им потребуется сделать дополнительную очистку до освобождения их памяти.

Финализаторы почти всегда используются для очистки ресурсов в случае, когда пользователь этого типа забыл распоряжаться им в порядке. Поэтому, если вы откроете FileStream, но забудете позвонить Dispose или Close, финализатор будет в конечном итоге освободите основной дескриптор файла для вас. В хорошо написанной программе финализаторы почти никогда не должны срабатывать, на мой взгляд.

Установка переменной в null

Одна маленькая точка на установке переменной для null - это почти никогда не требуется для сбора мусора. Иногда вы можете захотеть сделать это, если это переменная-член, хотя по моему опыту редко бывает, что «часть» объекта больше не нужна. Когда это локальная переменная, JIT обычно достаточно умна (в режиме выпуска), чтобы знать, когда вы больше не собираетесь использовать ссылку. Например:

StringBuilder sb = new StringBuilder(); 
sb.Append("Foo"); 
string x = sb.ToString(); 

// The string and StringBuilder are already eligible 
// for garbage collection here! 
int y = 10; 
DoSomething(y); 

// These aren't helping at all! 
x = null; 
sb = null; 

// Assume that x and sb aren't used here 

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

SomeObject foo = new SomeObject(); 

for (int i=0; i < 100000; i++) 
{ 
    if (i == 5) 
    { 
     foo.DoSomething(); 
     // We're not going to need it again, but the JIT 
     // wouldn't spot that 
     foo = null; 
    } 
    else 
    { 
     // Some other code 
    } 
} 

Реализация IDisposable/финализаторами

Таким образом, если ваши собственные типы реализации финализаторы? Почти наверняка нет. Если вы только косвенно удерживайте неуправляемые ресурсы (например,у вас есть FileStream в качестве переменной-члена), тогда добавление собственного финализатора не поможет: поток почти наверняка будет иметь право на сбор мусора, когда ваш объект, так что вы можете просто положиться на FileStream с финализатором (при необходимости - он может ссылаться на что-то еще и т. д.). Если вы хотите держать неуправляемый ресурс «почти» напрямую, SafeHandle - твой друг - для этого нужно немного времени, но это означает, что вы будете almostnever need to write a finalizer again. Обычно вам нужен только финализатор, если у вас есть действительно прямая ручка на ресурсе (), и вы должны посмотреть, как можно скорее перейти на SafeHandle. (Там есть две ссылки - читайте оба, в идеале.)

У Джо Даффи есть very long set of guidelines around finalizers and IDisposable (написано вместе с большим количеством умных людей), которые стоит прочитать. Стоит осознавать, что если вы запечатываете свои классы, это делает жизнь намного проще: шаблон переопределения Dispose для вызова нового виртуального метода Dispose(bool) и т. Д. Имеет значение только тогда, когда ваш класс предназначен для наследования.

Это было немного бродить, но, пожалуйста, попросите уточнить, где вы хотели бы некоторые :)

  0

Re «Единственный раз, когда может стоить установить локальную переменную в null» - возможно, также некоторые из более сложных сценариев «захвата» (множественные захваты одной и той же переменной), но, возможно, не стоит усложнять сообщение! +1 ... 22 фев. 092009-02-22 10:10:13

  0

@Marc: Это правда - я даже не думал о захваченных переменных. Хм. Да, я думаю, я оставлю это в покое;) 22 фев. 092009-02-22 10:19:50

+9

Wow. Я, наконец, понимаю источник культа скитистов. Это сообщение потрясающе! 24 фев. 092009-02-24 17:20:35

  0

Не могли бы вы рассказать, что произойдет, если вы установите «foo = null» в фрагменте кода выше? Насколько я знаю, эта строка только очищает значение переменной, указывающей на объект foo в управляемой куче? поэтому вопрос в том, что будет с foo объектом? не следует ли нам это распоряжаться? 25 апр. 102010-04-25 04:41:02

  0

@odiseh: Если объект был одноразовым, тогда да - вы должны его уничтожить. В этом разделе ответа речь шла только о сборке мусора, который полностью разделен. 25 апр. 102010-04-25 08:01:50

  0

+1, в частности, для: * «Стоит знать, что если вы запечатываете свои классы, это облегчает жизнь: шаблон переопределения Dispose для вызова нового виртуального метода Dispose (bool) и т. Д. Имеет значение только тогда, когда ваш класс предназначен для наследования ». * 04 мар. 142014-03-04 17:31:53

+1

Я искал разъяснения по некоторым проблемам, связанным с IDisposable, поэтому я нашел googled для« IDisposable Skeet »и нашел это. Большой! : D 06 янв. 152015-01-06 15:34:47

  0

@odiseh, после установки на нуль, вы не можете вызвать dispose, так как значение l становится null. если бы у этого объекта были ссылки на память, которые получили де-ссылку, то эти ссылки могут быть добавлены к генерации памяти, которая вскоре будет очищена сборщиком мусора. 23 июн. 172017-06-23 06:00:14


19

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

myclass = null; 

После выполнения этого, объект MyClass имел в виду все еще существует, и будет продолжать до тех пор, пока GC получает вокруг очистки его. Если Dispose явно вызван или находится в блоке использования, все ресурсы будут освобождены как можно скорее.

+7

Это * может * не все еще существовать после выполнения этой строки - это, возможно, было мусора * до * эта линия. JIT - это интеллектуальные линии, которые почти всегда не имеют отношения к делу. 22 фев. 092009-02-22 09:49:13

+6

Настройка на null может означать, что ресурсы, хранящиеся в объекте, * никогда * освобождены. GC не распоряжается, он только завершается, поэтому, если объект непосредственно содержит неуправляемые ресурсы, а его финализатор не распоряжается (или у него нет финализатора), тогда эти ресурсы будут течь. Что-то нужно знать. 22 фев. 092009-02-22 12:11:25


4

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

Когда вы вызываете Dispose(), это вызов метода для самого объекта. Независимо от метода Dispose, теперь делается на объекте. Но это не влияет на вашу ссылку на объект.

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

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

Вызов Dispose() на объект не «убивает» объект каким-либо образом. Он обычно используется для очистки, так что объект может быть безопасно удален впоследствии, но в конечном итоге нет ничего волшебного в Dispose, это просто метод класса.

  0

Я думаю, что этот ответ дополняет или является детальным ответом на «рекурсивный». 22 фев. 092009-02-22 01:08:12