Установка объекта в значение null vs Dispose()

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

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

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

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

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

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

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

3 ответов


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

Dispose, сбор мусора и финализации

когда вы пишите using заявление, это просто синтаксический сахар для блока try / finally, так что Dispose даже если код в теле using оператор создает исключение. Это не значит что объект-это мусор, собранный в конце блока.

ресурс неуправляемые ресурсы (ресурсы оперативной памяти). Это могут быть дескрипторы пользовательского интерфейса, сетевые подключения,дескрипторы файлов и т. д. Это ограниченные ресурсы, поэтому вы обычно хотите выпустить их как можно скорее. Вы должны реализовать IDisposable всякий раз, когда ваш тип "владеет" неуправляемым ресурсом, либо напрямую (обычно через IntPtr) или косвенно (например, через 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/финализаторы

итак, должны ли ваши собственные типы реализовывать финализаторы? Почти наверняка нет. Если бы ты только косвенно удерживайте неуправляемые ресурсы (например, у вас есть FileStream как переменная-член), то добавление собственного финализатора не поможет: поток почти наверняка будет иметь право на сборку мусора, когда ваш объект, поэтому вы можете просто положиться на FileStream наличие финализатора (при необходимости - он может ссылаться на что-то другое, и т. д.). Если вы хотите провести неуправляемый ресурс" почти " напрямую,SafeHandle это ваш друг-это занимает немного времени, чтобы начать, но это означает, что вы будете почти не нужно писать финализатор снова. Обычно вам нужен только финализатор, если у вас есть действительно прямой дескриптор ресурса (IntPtr) и вы должны смотреть, чтобы перейти к SafeHandle как можно скорее. (Там есть две ссылки - прочитайте обе, в идеале.)

у Джо Даффи есть очень долго набор рекомендаций вокруг финализаторов и IDisposable (в соавторстве с большим количеством умных людей), которые стоит прочитать. Стоит помнить, что если вы запечатываете свои классы, это делает жизнь намного проще: шаблон переопределения Dispose для вызова нового виртуального Dispose(bool) метод etc актуален только тогда, когда ваш класс предназначен для наследования.

это было немного бессвязно, но, пожалуйста, попросите разъяснений, где вы хотели бы некоторые:)


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

myclass = null;

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


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

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

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

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

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