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