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