Используются ли методы деструктора сборщиком мусора неявно, а методы удаления используются разработчиками для явного удаления объектов?

Я вижу, что уже есть много потоков, посвященных методам удаления и деструктора, но я просто хочу убедиться, что я правильно их понимаю, прежде чем двигаться дальше.

Используются ли методы деструктора неявно сборщиком мусора, когда на объекты больше не ссылаются (т. е. они больше не нужны), и методы удаления, используемые нами, разработчиками, для явного удаления объектов, которые не могут быть обработаны сборщиком мусора?

Кроме того, я читаю все это сейчас, и кажется, что это тот или иной случай с этими методами. Так, например, указан следующий код:

class DansClass : IDisposable
{
    public void Dispose()
    {
        GC.SuppressFinalize(this);
        Console.WriteLine("Disposing...");
    }

    -DansClass()
    {
        Console.WriteLine("Destructing...");
    }
}

Вывод будет:

Уничтожение...

Понятно, поскольку мы подавили финализацию (деструктор), поэтому мы видим только вывод Dispose.

Но если я закомментирую метод SuppressFinalize(), результат будет таким:

Утилизация...

Почему не вызывается деструктор?


person Community    schedule 04.01.2012    source источник
comment
@Daniel Если вы видели «много тем» по этим двум вопросам, почему вы повторяете одно и то же?   -  person George Stocker    schedule 04.01.2012
comment
@GeorgeStocker Потому что из других тем я пытаюсь выработать собственное понимание - и я не видел, чтобы ни одна из других тем не затрагивала проблему, которую я поднял во 2-й половине моего вопроса. Если вас это так сильно напрягает, то покиньте тему.   -  person    schedule 04.01.2012


Ответы (2)


Методы Dispose, финализаторы и деструкторы C# с ироничными названиями существуют потому, что многие объекты просят другие сущности делать что-то от их имени до дальнейшего уведомления (как правило, предоставляя монопольное использование чего-то вроде файла, области памяти, коммуникационного потока, аппаратного устройства, дескриптор GDI и т. д.); если объект, выдающий такой запрос, исчезнет, ​​а другие сущности не узнают, что их услуги больше не требуются, все, что было отложено от имени покинутого объекта, останется бесполезно недоступным.

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

К сожалению, по разным причинам (большинства из которых легко избежать, а некоторых нет) объекты иногда оставляются без вызова IDisposable.Dispose. Это приведет к тому, что внешним сущностям придется бесполезно продолжать действовать, по крайней мере, какое-то время, от имени брошенных объектов. Чтобы избежать того, чтобы внешние объекты постоянно действовали от имени внешних объектов, система может уведомлять объекты о том, что они были брошены, тем самым давая им возможность сообщить об этом внешним объектам. Всякий раз, когда создается объект, чей класс переопределяет Object.Finalize, он будет помещен в специальный список объектов, которые хотят получить уведомление, если они будут заброшены. Нахождения в этом списке само по себе недостаточно, чтобы объект считался «живым», но перед тем, как сборщик мусора удалит мертвые объекты из памяти, он проверит, есть ли они в списке уведомлений перед уничтожением. Все мертвые объекты, находящиеся в списке, будут перемещены из списка объектов, требующих уведомления, если/когда они будут брошены, в список объектов, которым необходимо сообщить, что они были брошены. Помещение в этот второй список приведет к тому, что мертвые объекты и любые объекты, на которые они содержат ссылки, снова будут считаться «живыми», по крайней мере, до тех пор, пока уведомление не будет выполнено. Однако когда они перемещаются во второй список, они удаляются из первого списка, так что, если позже будет обнаружено, что они снова мертвы, они будут стерты из памяти без дальнейшего уведомления.

После завершения сборки мусора, если какие-либо объекты находятся в списке объектов, требующих уведомления об отказе, система вызовет метод Object.Finalize для каждого такого объекта. Как правило, после вызова Object.Finalize для такого объекта больше не будет корневых ссылок на него, и он исчезнет при следующей сборке мусора. Однако объекты могут быть воскрешены.

В vb.net и, насколько мне известно, в большинстве языков .net Object.Finalize переопределяется простым объявлением переопределения обычным способом. По какой-то причине создатели C# решили это запретить. Вместо этого в C# необходимо использовать языковую структуру, по иронии судьбы называемую «Деструктор», для переопределения Finalize, чтобы объекты, которые оказались заброшенными, не уничтожались без уведомления, а вместо этого получали шанс почиститься.

В реальной работе Object.Finalize есть много хитростей, и лучше не полагаться на нее, за исключением случаев крайней необходимости. Иронично названные «деструкторы» на самом деле не уничтожают объекты, а задерживают их уничтожение. Вызов GC.SuppressFinalize() для объекта удалит его из списка объектов, запрашивающих уведомление, когда они оставлены; при условии, что Dispose вызывается для объекта, а он, в свою очередь, вызывает GC.SuppressFinalize() в качестве своей последней операции, нет особого вреда в том, чтобы объект переопределял Finalize или объявлял "деструктор", но в целом лучше всего переопределять Finalize (или объявлять "деструкторы") только в относительно простых классах.

person supercat    schedule 04.01.2012

Что ж, поведение, которое вы видите, связано с тем, что Finalizers (то, что вы деструктор, также известный как финализатор в .Net), ставятся в очередь для запуска сборщиком мусора в фоновом режиме.

В конце концов он будет вызван (хотя в некоторых случаях может и не быть). Вы можете принудительно выполнить их, однако, чтобы понять, что происходит:

// Assuming you remove the GC.SuppressFinalize call
myDisposable.Dispose();

GC.WaitForPendingFinalizers();
// Output:
// Disposing...
// Destructing...

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

Для правильной реализации шаблона IDisposable требуется:

  • Dispose очищает как неуправляемые, так и управляемые ресурсы.
  • Финализатор очищает зависшие, неуправляемые ресурсы (если их нет, он не нужен)
  • Ни один из них не может генерировать исключение, Dispose должен вызываться более одного раза.
  • Финализатор должен вызывать реализацию Dispose
  • Специфичные для домена методы завершения, такие как Close, должны быть функционально эквивалентны Dispose, если оба существуют.
person user7116    schedule 04.01.2012
comment
То, что вы называете деструктором, известно как финализатор в .Net, а финализатор известен как деструктор в С#... - person Henk Holterman; 04.01.2012
comment
Я добавил недостающее также. Я стараюсь избегать destructor, чтобы избежать путаницы с семантикой C++. - person user7116; 04.01.2012
comment
@Henk Holterman: деструктор C# - это языковая конструкция с ироничным названием (и глупая, ИМХО), которая указывает компилятору переопределить Object.Finalize с помощью метода, который будет включать предоставленный код, но также делать несколько других вещей, в первую очередь вызывая base.Finalize(). Хотя деструкторы C# вызывают переопределение Finalize, это не совсем то же самое, что финализаторы. - person supercat; 04.01.2012
comment
@superca - они ближе всего к Finalizer для программиста на C #, поскольку Finalize не существует. C# не имеет (других) финализаторов. И ~ClassName(){} официально является деструктором, нравится вам это или нет. - person Henk Holterman; 04.01.2012
comment
@HenkHolterman: я знаю, что C #, к сожалению, запрещает программистам переопределять Object.Finalize, и поэтому конструкция деструктора с ироничным названием - единственный способ получить к ним доступ. Однако я думаю, что важно отметить, что деструктор C# и Finalizer не совсем синонимы. Помимо прочего, вызов base.Finalize() выполняется после выполнения предоставленного программистом кода в деструкторе, но не выполняется после выполнения финализатора. - person supercat; 04.01.2012
comment
@supercat - Так что C # не позволит вам написать сломанный Finalizer, большое дело ... См. Здесь msdn.microsoft.com/en-us/library/ - person Henk Holterman; 04.01.2012
comment
@HenkHolterman: C# допускает множество способов написания неработающего кода финализации. Что касается вызова base.Finalize(), требование его вызова не означает, что он не должен быть обернут другим кодом или блоком try. Шаблон Microsoft Dispose делает проблему в значительной степени спорной, поскольку Finalize() не должна содержать никакой логики, и нет необходимости, чтобы производный класс переопределял ее, если это делает родительский класс. - person supercat; 05.01.2012