Поскольку у .NET есть сборщик мусора, зачем нам нужны финализаторы/деструкторы/dispose-pattern?

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

Так как это так, почему тогда некоторые объекты должны иметь деструктор или метод удаления? Не удастся ли запустить среду после них, когда они больше не будут ссылаться?

Ответ 1

Финализаторы необходимы, чтобы гарантировать выход ограниченных ресурсов обратно в систему, такие как дескрипторы файлов, сокеты, объекты ядра и т.д. Так как финализатор всегда работает в конце жизни объектов, это назначенное место для выпуска этих ручек.

Шаблон Dispose используется для обеспечения детерминированного уничтожения ресурсов. Поскольку сборщик мусора в режиме времени .net не является детерминированным (это означает, что вы никогда не можете быть уверены, когда среда выполнения будет собирать старые объекты и вызывать их финализатор), необходим метод для обеспечения детерминированного выпуска системных ресурсов. Поэтому, когда вы правильно реализуете шаблон Dispose, вы предоставляете детерминированный выпуск ресурсов, а в тех случаях, когда потребитель неосторожен и не распоряжается объектом, финализатор очистит объект.

Простым примером того, почему требуется Dispose, может быть быстрый и грязный метод журнала:

public void Log(string line)
{
    var sw = new StreamWriter(File.Open(
        "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None));

    sw.WriteLine(line);

    // Since we don't close the stream the FileStream finalizer will do that for 
    // us but we don't know when that will be and until then the file is locked.
}

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

Правильный способ заключается в том, чтобы уничтожить объект, когда он выполняется с ним:

public void Log(string line)
{
    using (var sw = new StreamWriter(File.Open(
        "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))) {

        sw.WriteLine(line);
    }

    // Since we use the using block (which conveniently calls Dispose() for us)
    // the file well be closed at this point.
}

BTW, технически финализаторы и деструкторы означают одно и то же; Я предпочитаю называть финализаторы С# деструкторов, поскольку в противном случае они склонны смешивать людей с деструкторами С++, которые в отличие от С# являются детерминированными.

Ответ 2

Предыдущие ответы хороши, но позвольте мне еще раз подчеркнуть важный момент. В частности, вы сказали, что

Если я правильно понимаю, время выполнения .net всегда будет очищаться после меня.

Это лишь отчасти правильно. На самом деле .NET предлагает только автоматическое управление для одного конкретного ресурса: основная память. Все остальные ресурсы нуждаются в ручной очистке. 1)

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


1) Обычное попытка - связать время жизни других ресурсов со временем жизни мест памяти или идентификаторов в коде - отсюда и наличие финализаторов.

Ответ 3

Сборщик мусора будет работать только в том случае, если система не находится под давлением памяти, если только не нужно освобождать память. Это означает, что вы никогда не сможете быть уверены, когда GC будет работать.

Теперь представьте, что вы подключаетесь к базе данных. Если вы позволите очистить GC после вас, вы можете быть подключены к базе данных намного дольше, чем это необходимо, вызывая странную нагрузку. В этом случае вы хотите реализовать IDisposable, чтобы пользователь мог вызвать Dispose() или использовать с помощью(), чтобы действительно убедиться, что соединение закрыто как можно скорее, не полагаясь на GC, который может работать намного позже.

Как правило, IDisposable реализуется в любом классе, который работает с неуправляемыми ресурсами.

Ответ 4

  • Есть вещи, которые сборщик мусора не может очистить после вас.
  • Даже с вещами, которые он может очистить, вы можете помочь ему быстрее очистить

Ответ 5

Настоящая причина заключается в том, что сборка мусора .net НЕ предназначена для сбора неуправляемых ресурсов, поэтому очистка этих ресурсов по-прежнему лежит в руках разработчика. Кроме того, финализаторы объектов не вызывается автоматически, когда объект выходит из области видимости. Они вызываются GC в какое-то неопределенное время. И когда их вызывают, GC не запускает ее сразу, она ждет следующего раунда, чтобы называть ее, увеличивая время на очистку еще больше, не очень хорошо, когда ваши объекты содержат ограниченные неуправляемые ресурсы (например, файлы или сетевые соединения). Введите шаблон одноразового использования, в котором разработчик может вручную освободить ограниченные ресурсы в определенное время (при вызове функции yourobject.Dispose() или использования (...)). Имейте в виду, что вы должны вызвать GC.SuppressFinalize(this); в вашем методе размещения, чтобы сообщить GC, что объект был вручную удален и не должен быть завершен. Я предлагаю вам взглянуть на книгу "Руководства по разработке каркаса" К. Квалины и Б. Абрамса. Он очень хорошо описывает одноразовый шаблон.

Удачи!

Ответ 6

Простейшее объяснение:

  • Dispose предназначен для детерминированного удаления ресурсов без памяти, особенно ограниченных ресурсов. Например, дескриптор окна или соединение с базой данных.
  • Finalize предназначен для не детерминированного удаления ресурсов без памяти, обычно в качестве блокиратора обратного вызова, если Dispose не был вызван.

Некоторые рекомендации по реализации метода Finalize:

  • Только реализовать Finalize для объектов, требующих завершения, потому что есть затраты на производительность, связанные с методами Finalize.
  • Если вам нужен метод Finalize, подумайте о том, чтобы реализовать IDisposable, чтобы позволить пользователям вашего типа избегать затрат на вызов метода Finalize.
  • Ваши методы Finalize должны быть защищены, а не публичными.
  • Ваш метод Finalize должен освобождать любые внешние ресурсы, которыми владеет тип, но только те, которыми он владеет. Он не должен ссылаться на другие ресурсы.
  • CLR не дает никаких гарантий относительно порядка, в котором вызывается метод Finalize. Как отмечает Даниэль в своем комментарии, это означает, что метод Finalize не должен иметь доступ к каким-либо ссылочным типам участников, если это возможно, потому что они могут иметь (или могут когда-нибудь) свои собственные финализаторы.
  • Никогда не вызывайте метод Finalize непосредственно на любом типе, отличном от типа базового типа.
  • Старайтесь избегать любого необработанного исключения в вашем методе Finalize, так как это прекратит ваш процесс (в версии 2.0 или выше).
  • Избегайте выполнения какой-либо долговременной задачи в вашем методе Finalizer, поскольку это блокирует поток Finalizer и предотвращает выполнение других методов Finalizer.

Некоторые рекомендации по реализации метода Dispose:

  • Реализовать шаблон дизайна dispose для типа, который инкапсулирует ресурсы, которые явно необходимо освободить.
  • Реализовать шаблон дизайна dispose на базовом типе, который имеет один или несколько производных типов, которые хранятся на ресурсах, даже если базовый тип этого не делает.
  • После того, как Dispose вызывается в экземпляре, запретите запуск метода Finalize, вызвав метод GC.SuppressFinalize. Единственным исключением из этого правила является редкая ситуация, в которой работа должна выполняться в Finalize, которая не покрывается Dispose.
  • Не предполагайте, что Dispose будет вызываться. Неуправляемые ресурсы, принадлежащие типу, также должны быть выпущены в методе Finalize в случае, если Dispose не вызывается.
  • Выбросьте ObjectDisposedException из методов экземпляра этого типа (кроме Dispose), когда ресурсы уже установлены. Это правило не применяется к методу Dispose, потому что оно должно быть вызвано несколько раз, не выбрасывая исключение.
  • Распространять вызовы Dispose через иерархию базовых типов. Метод Dispose должен освобождать все ресурсы, удерживаемые этим объектом и любым объектом, принадлежащим этому объекту.
  • Вы должны не учитывать возможность использования объекта после вызова метода Dispose. Повторное создание объекта, который уже был удален, является сложным шаблоном для реализации.
  • Разрешить метод Dispose вызываться более одного раза без исключения исключения. После первого вызова метод ничего не должен делать.

Ответ 7

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

Посмотрите документацию MSDN для IDisposable; http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

В примере используется не управляемый обработчик - IntPr.

Ответ 8

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

Ответ 9

В основном для неконтролируемого кода и взаимодействия с не управляемым кодом. Управляемый код "Pure" никогда не нуждается в финализаторе. Одноразовая с другой стороны - это просто удобный шаблон, который заставит что-то освободиться, когда вы закончите с ним.

Ответ 10

Сборщик мусора .NET знает, как обрабатывать управляемые объекты в среде выполнения .NET. Но шаблон Dispose (IDisposable) используется в основном для не управляемых объектов, которые использует приложение.

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

Ответ 11

Есть несколько (очень немногих) случаев, когда может потребоваться выполнить определенное действие, когда чистый управляемый объект больше не используется, я не могу придумать пример с верхней части головы, но у меня есть на протяжении многих лет видел пару законных целей. Но главная причина - очистить любые неуправляемые ресурсы, которые может использовать объект.

Итак, в общем случае вам не нужно использовать шаблон Dispose/Finalize, если вы не используете неуправляемые ресурсы.

Ответ 12

Поскольку сборщик мусора не может собрать то, что не удалось выделить управляемой среде. Поэтому любой вызов неуправляемого API, который приводит к распределению памяти, должен собираться старомодным способом.