Установка объекта в нуль против Dispose () - PullRequest
100 голосов
/ 22 февраля 2009

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

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

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

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

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

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

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

Ответы [ 3 ]

199 голосов
/ 22 февраля 2009

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

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) метода и т. Д. Уместен, только если ваш класс предназначен для наследования.

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

21 голосов
/ 22 февраля 2009

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

myclass = null;

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

5 голосов
/ 22 февраля 2009

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

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

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

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

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

...