Ответ 1
Важно отделить удаление от сбора мусора. Это совершенно разные вещи, с одной точкой, с которой я приду через минуту.
Dispose
, сбор и завершение сборки мусора
Когда вы пишете оператор using
, он просто синтаксический сахар для блока try/finally, так что Dispose
вызывается, даже если код в теле оператора using
вызывает исключение. Это не означает, что объект представляет собой мусор, собранный в конце блока.
Утилизация - это неуправляемые ресурсы (ресурсы без памяти). Это могут быть интерфейсы UI, сетевые подключения, дескрипторы файлов и т.д. Это ограниченные ресурсы, поэтому вы обычно хотите их освободить, как только сможете. Вы должны внедрять IDisposable
, когда ваш тип "владеет" неуправляемым ресурсом, либо напрямую (обычно через IntPtr
), либо косвенно (например, через Stream
, a SqlConnection
и т.д.).
Сама коллекция мусора - это только память - с одной маленькой завихренностью. Сборщик мусора может находить объекты, на которые больше нельзя ссылаться, и освободить их. Он не ищет мусора все время, хотя - только тогда, когда он обнаруживает, что ему нужно (например, если одно "поколение" кучи заканчивается из памяти).
Закрутка завершается. Сборщик мусора ведет список объектов, которые уже недоступны, но которые имеют финализатор (написанный как ~Foo()
в С#, несколько смутно - они не похожи на деструкторы С++). Он запускает финализаторы на этих объектах, на случай, если им потребуется дополнительная очистка до освобождения их памяти.
Финализаторы почти всегда используются для очистки ресурсов в случае, когда пользователь этого типа забыл распоряжаться им в порядке. Поэтому, если вы откроете 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/finalizers
Итак, если ваши собственные типы реализуют финализаторы? Почти наверняка нет. Если вы косвенно владеете неуправляемыми ресурсами (например, у вас есть FileStream
как переменная-член), то добавление собственного финализатора не поможет: поток почти наверняка будет иметь право на сбор мусора, когда ваш объект, так что вы можете просто полагайтесь на FileStream
, имеющий финализатор (при необходимости - он может ссылаться на что-то еще и т.д.). Если вы хотите сохранить неуправляемый ресурс "почти" напрямую, SafeHandle
- ваш друг - для этого требуется немного времени, но это означает, что вы почти больше не нужно писать финализатор. Обычно вам нужен только финализатор, если у вас есть действительно прямой дескриптор ресурса (IntPtr
), и вы должны перейти к SafeHandle
, как только сможете. (Здесь есть две ссылки - читайте оба, в идеале.)
Joe Duffy имеет очень длинный набор рекомендаций вокруг финализаторов и IDisposable (соавторами с большим количеством умных людей), которые стоит прочитать. Стоит осознавать, что если вы запечатываете свои классы, это значительно облегчает жизнь: шаблон переопределения Dispose
для вызова нового виртуального метода Dispose(bool)
и т.д. Имеет значение только тогда, когда ваш класс предназначен для наследования.
Это было немного странно, но, пожалуйста, попросите уточнить, где вы хотите:)