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