Поскольку в .NET есть сборщик мусора, зачем нам финализаторы / деструкторы / dispose-pattern? - PullRequest
67 голосов
/ 01 декабря 2008

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

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

Ответы [ 12 ]

93 голосов
/ 01 декабря 2008

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

Шаблон 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.
}

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

20 голосов
/ 01 декабря 2008

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

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

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

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


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

9 голосов
/ 01 декабря 2008

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

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

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

4 голосов
/ 01 декабря 2008
  1. Есть вещи, которые сборщик мусора не может почистить после вас
  2. Даже с такими вещами, как он может очистить, вы можете помочь очистить его раньше
2 голосов
/ 01 декабря 2008

Упрощенное объяснение:

  • Утилита предназначена для детерминированного удаления не связанных с памятью ресурсов, особенно дефицитных ресурсов . Например, дескриптор окна или соединение с базой данных.
  • 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 более одного раза без исключения. Метод не должен делать ничего после первого вызова.
2 голосов
/ 01 декабря 2008

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

Удачи!

1 голос
/ 01 декабря 2008

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

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

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

0 голосов
/ 01 декабря 2008

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

0 голосов
/ 01 декабря 2008

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

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

0 голосов
/ 01 декабря 2008

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

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

...