Завершить против утилизации


215

Почему некоторые люди используют Finalizeметод над Disposeметодом?

В каких ситуациях вы будете использовать Finalizeметод поверх Disposeметода и наоборот?


Ответы:


121

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

Некоторые типы инкапсулируют одноразовые ресурсы таким образом, чтобы их было легко использовать и избавляться от них одним действием. Общее использование часто так: открыть, прочитать или написать, закрыть (удалить). Это очень хорошо вписывается в usingконструкцию.

Другие немного сложнее. WaitEventHandlesпоскольку экземпляры не используются, как это, поскольку они используются для передачи сигналов от одного потока к другому. Тогда возникает вопрос: кому это нужно Dispose? В качестве защитных типов, подобных этим, реализуется Finalizeметод, который обеспечивает удаление ресурсов, когда приложение больше не ссылается на экземпляр.


60
Я не мог понять этот одобренный ответ. Я все еще хочу знать другое. Что это?
Исмаэль

22
@Ismael: Самая большая ситуация, когда это Finalizeможет быть оправдано, - это когда несколько объектов заинтересованы в том, чтобы ресурс оставался в живых, но нет средства, с помощью которого объект, который перестает интересоваться ресурсом, может узнать, является ли он последний. В таком случае Finalizeобычно срабатывает только когда никто не заинтересован в объекте. Потеря времени работы Finalizeужасна для не заменимых ресурсов, таких как файлы и блокировки, но может подойти для заменимых ресурсов.
суперкат

13
+1 суперкату за отличное новое (для меня) слово. Контекст прояснил это довольно ясно, но на всякий случай для остальных из нас вот что говорит Википедия: «Фунгируемость - это свойство товара или товара, отдельные единицы которого способны к взаимозаменяемости, такие как сладкая сырая нефть, разделяет компания, облигации, драгоценные металлы или валюты ".
Джон Кумбс

5
@JonCoombs: Это в значительной степени верно, хотя, возможно, стоит отметить, что термин «заменимый ресурс» применяется к вещам, которые могут быть свободно заменены до тех пор, пока они не будут приобретены и снова станут свободно заменяемыми после выпуска или отмены . Если в системе имеется пул объектов блокировки, и код получает объект, который она ассоциирует с какой-либо сущностью, то, пока кто-либо удерживает эту ссылку на эту блокировку с целью ассоциирования ее с этой сущностью , эта блокировка не может быть заменена любой другой. Если весь код, который заботится об охраняемом объекте, покидает блокировку, то ...
суперкат

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

135

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

С Disposeдругой стороны, метод должен вызываться кодом, который создал ваш класс, чтобы вы могли очистить и освободить любые полученные вами ресурсы (неуправляемые данные, соединения с базой данных, файловые дескрипторы и т. Д.) В момент выполнения кода ваш объект.

Стандартная практика заключается в реализации IDisposableи Disposeтак, чтобы вы могли использовать свой объект в usingотчете. Такие как using(var foo = new MyObject()) { }. И в финализаторе вы звоните Dispose, на случай, если код вызова забыл вас утилизировать.


17
Вы должны быть немного осторожнее при вызове Dispose из своей реализации Finalize - Dispose может также распоряжаться управляемыми ресурсами, которые вы не хотите трогать из своего финализатора, поскольку они, возможно, уже были завершены сами.
Itowlson

6
@itowlson: проверка на нулевое значение в сочетании с предположением, что объекты могут быть удалены дважды (второй вызов ничего не делает), должна быть достаточно хорошей.
Самуил

7
Стандартный шаблон IDisposal и скрытая реализация Dispose (bool) для обработки необязательных управляемых компонентов, по-видимому, решают эту проблему.
Броуди

Похоже, что нет причин реализовывать деструктор (метод ~ MyClass ()), а всегда реализовывать и вызывать метод Dispose (). Или я не прав? Может ли кто-нибудь дать мне пример, когда оба должны быть реализованы?
dpelisek

66

Finalize - это метод backstop, вызываемый сборщиком мусора, когда он возвращает объект. Dispose - это метод «детерминированной очистки», который вызывается приложениями для освобождения ценных собственных ресурсов (дескрипторов окон, соединений с базами данных и т. Д.), Когда они больше не нужны, вместо того, чтобы оставлять их удерживаемыми на неопределенный срок до тех пор, пока GC не дойдет до объекта.

Как пользователь объекта, вы всегда используете Dispose. Финализация для GC.

Как разработчик класса, если вы располагаете управляемыми ресурсами, которые должны быть удалены, вы реализуете Dispose. Если у вас есть собственные ресурсы, вы реализуете и Dispose, и Finalize, и оба вызываете общий метод, который освобождает собственные ресурсы. Эти идиомы обычно объединяются с помощью частного метода Dispose (bool dispose), который Dispose вызывает с помощью true и завершает вызовы с помощью false. Этот метод всегда освобождает собственные ресурсы, затем проверяет параметр распоряжения, и если он истинен, он удаляет управляемые ресурсы и вызывает GC.SuppressFinalize.



2
Первоначальный рекомендуемый образец для классов, в которых содержалось сочетание самоочищающихся («управляемых») и не самоочищающихся («неуправляемых») ресурсов, давно устарел. Лучшим вариантом является отдельная упаковка каждого неуправляемого ресурса в собственный управляемый объект, который не содержит строгих ссылок на что-либо, что не является необходимым для его очистки. Все, на что финализуемый объект имеет прямую или косвенную сильную ссылку, будет иметь увеличенное время жизни GC. Инкапсуляция вещей, которые необходимы для очистки, позволит избежать продления срока службы GC вещей, которых нет.
суперкат

2
@JCoombs: Disposeэто хорошо, и правильно его реализовать, как правило, легко. Finalizeэто зло, и правильно его реализовать, как правило, сложно. Помимо прочего, поскольку сборщик мусора гарантирует, что идентификация объекта никогда не будет «переработана», пока существует какая-либо ссылка на этот объект, легко очистить группу Disposableобъектов, некоторые из которых, возможно, уже были очищены, без проблем; любая ссылка на объект, для которого Disposeуже был вызван, останется ссылкой на объект, для которого Disposeуже был вызван.
суперкат

2
@JCoombs: неуправляемые ресурсы, напротив, обычно не имеют такой гарантии. Если объект Fredвладеет файловым дескриптором № 42 и закрывает его, система может присоединить тот же номер к некоторому файловому дескриптору, который присвоен другому объекту. В этом случае дескриптор файла № 42 будет ссылаться не на закрытый файл Фреда, а на файл, который активно использовался этим другим объектом; для , Fredчтобы попытаться закрыть ручку # 42 снова будет иметь катастрофические последствия . Попытка 100% надежного отслеживания того, был ли освобожден один неуправляемый объект, осуществима. Пытаться отслеживать несколько объектов гораздо сложнее.
суперкат

2
@JCoombs: если каждый неуправляемый ресурс помещается в свой собственный объект-обертку, который ничего не делает, кроме как управляет его временем жизни, то внешний код, который не знает, был ли ресурс освобожден, но знает, что так и должно быть, если он еще не был , можете смело просить объект-обертку освободить его; объект-обертка будет знать, сделал ли он это, и сможет выполнить или проигнорировать запрос. Тот факт, что GC гарантирует, что ссылка на обертку всегда будет действительной ссылкой на обертку, является очень полезной гарантией.
суперкат

43

Доработка

  • Финализаторы всегда должны быть protected, нет publicили нет, privateчтобы метод не мог быть вызван напрямую из кода приложения, и в то же время он может вызывать base.Finalizeметод
  • Финализаторы должны освобождать только неуправляемые ресурсы.
  • Фреймворк не гарантирует, что финализатор будет выполняться вообще в любом конкретном случае.
  • Никогда не выделяйте память в финализаторах и не вызывайте виртуальные методы из финализаторов.
  • Избегайте синхронизации и создания необработанных исключений в финализаторах.
  • Порядок выполнения финализаторов недетерминирован - другими словами, вы не можете полагаться на другой объект, все еще доступный в вашем финализаторе.
  • Не определяйте финализаторы для типов значений.
  • Не создавайте пустых деструкторов. Другими словами, вы никогда не должны явно определять деструктор, если вашему классу не нужно очищать неуправляемые ресурсы, и если вы его определите, он должен выполнить некоторую работу. Если позже вам больше не нужно очищать неуправляемые ресурсы в деструкторе, удалите его полностью.

Dispose

  • Реализуйте IDisposableна каждом типе, который имеет финализатор
  • Убедитесь, что объект стал непригодным для использования после вызова Disposeметода. Другими словами, избегайте использования объекта после вызова Disposeметода.
  • Позвоните Disposeна все IDisposableтипы, как только вы закончите с ними
  • Позволяют звонить Disposeнесколько раз, не вызывая ошибок.
  • Подавить последующие вызовы финализатора из Disposeметода, используя GC.SuppressFinalizeметод
  • Избегайте создания одноразовых типов стоимости
  • Избегайте создания исключений изнутри Disposeметодов

Утилизировать / Завершить шаблон

  • Microsoft рекомендует вам реализовать как Disposeи Finalizeпри работе с неуправляемыми ресурсами. FinalizeРеализация будет работать и ресурсы будут по- прежнему будут выпущены , когда объект мусора , даже если разработчик забыл вызвать Disposeметод явно.
  • Очистите неуправляемые ресурсы как в Finalizeметоде, так и в Disposeметоде. Дополнительно вызовите Disposeметод для любых объектов .NET, которые есть у вас в качестве компонентов внутри этого класса (с неуправляемыми ресурсами в качестве их члена) из Disposeметода.

17
Я везде читаю один и тот же ответ и до сих пор не могу понять, какова цель каждого из них. Я читаю правила после правил, не более того.
Исмаэль

@Ismael: а также автор не добавляет ничего, кроме копирования и вставки текста из MSDN.
Тарик,

@tarik Я это уже выучил. В тот раз у меня было «обещание» зачатия.
Исмаэль

31

Finalize вызывается GC, когда этот объект больше не используется.

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

Если пользователь забыл вызвать Dispose и если в классе реализован Finalize, то GC позаботится о том, чтобы он был вызван.



19

Вот некоторые ключи из книги MCSD Certification Toolkit (экзамен 70-483), стр. 193:

деструктор ≈ (он почти равен)base.Finalize() , деструктор преобразуется в переопределенную версию метода Finalize, который выполняет код деструктора и затем вызывает метод Finalize базового класса. Тогда это совершенно недетерминированный, вы не можете знать, когда будет вызван, потому что зависит от GC.

Если класс не содержит управляемых ресурсов и неуправляемых ресурсов , он не должен реализовывать IDisposableили иметь деструктор.

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

Если у класса есть только неуправляемые ресурсы , он должен быть реализован IDisposableи нуждается в деструкторе, если программа не вызывает Dispose().

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

Dispose()должен освободить как управляемые, так и неуправляемые ресурсы .

Деструктор должен освобождать только неуправляемые ресурсы . Когда деструктор выполняется, вы не можете быть уверены, что управляемые объекты все еще существуют, поэтому вы все равно не можете вызывать их методы Dispose. Это достигается путем использования канонического protected void Dispose(bool disposing)шаблона, когда освобождаются (удаляются) только управляемые ресурсы disposing == true.

После освобождения ресурсов Dispose()следует вызватьGC.SuppressFinalize , чтобы объект мог пропустить очередь завершения.

Пример реализации для класса с неуправляемыми и управляемыми ресурсами:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {
        FreeResources(true);

        // We don't need the destructor because
        // our resources are already freed.
        GC.SuppressFinalize(this);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;
        }
    }
}

2
Это хороший ответ! Но я думаю, что это неправильно: «деструктор должен вызвать GC.SuppressFinalize». Вместо этого, не должен ли публичный метод Dispose () вызвать GC.SuppressFinalize? См .: docs.microsoft.com/en-us/dotnet/api/… Вызов этого метода не позволяет сборщику мусора вызывать Object.Finalize (который переопределяется деструктором).
Ева

7

В 99% случаев вам не нужно беспокоиться об этом. :) Но, если ваши объекты содержат ссылки на неуправляемые ресурсы (например, дескрипторы окон, дескрипторы файлов), вам нужно предоставить способ, которым управляемый объект освобождает эти ресурсы. Finalize дает неявный контроль над высвобождением ресурсов. Вызывается сборщиком мусора. Dispose - это способ явно контролировать освобождение ресурсов и может быть вызван напрямую.

Намного больше узнать о предмете сбора мусора , но это только начало.


5
Я уверен, что более 1% приложений на C # используют базы данных: где вам нужно беспокоиться о IDisposable SQL.
Самуил

1
Кроме того, вы должны реализовать IDisposable, если вы инкапсулируете IDisposables. Что, вероятно, покрывает остальные 1%.
Даррен Кларк

@ Самуэль: Я не вижу, что с этим связано. Если вы говорите о закрытии соединения, это нормально, но это другое дело. Вам не нужно своевременно распоряжаться объектами, чтобы закрыть соединения.
JP Alioto

1
@JP: Но шаблон Using (...) значительно упрощает управление.
Броуди

2
Договорились, но в том-то и дело. Шаблон использования скрывает вызов Dispose для вас.
JP Alioto

6

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

Корректная реализация финализатора общеизвестно трудна, и ее следует избегать везде, где это возможно - SafeHandleкласс (доступен в .Net v2.0 и более поздних версиях) теперь означает, что вам очень редко (если вообще когда-либо) нужно реализовывать финализатор больше.

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

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

См. Обновление DG: Распоряжение, Финализация и Управление ресурсами для того, что я считаю лучшим и наиболее полным набором рекомендаций по финализаторам и IDisposable.


3

Резюме -

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

Кроме того, еще одно отличие состоит в том, что в реализации Dispose () вы также должны освобождать управляемые ресурсы , тогда как в Finalizer это делать не следует. Это связано с тем, что весьма вероятно, что управляемые ресурсы, на которые ссылается объект, уже очищены до того, как будут готовы к финализации.

Для класса, который использует неуправляемые ресурсы, рекомендуется определить оба метода - Dispose () и Finalizer - для использования в качестве запасного варианта на случай, если разработчик забудет явно избавиться от объекта. Оба могут использовать общий метод для очистки управляемых и неуправляемых ресурсов:

class ClassWithDisposeAndFinalize : IDisposable
{
    // Used to determine if Dispose() has already been called, so that the finalizer
    // knows if it needs to clean up unmanaged resources.
     private bool disposed = false;

     public void Dispose()
     {
       // Call our shared helper method.
       // Specifying "true" signifies that the object user triggered the cleanup.
          CleanUp(true);

       // Now suppress finalization to make sure that the Finalize method 
       // doesn't attempt to clean up unmanaged resources.
          GC.SuppressFinalize(this);
     }
     private void CleanUp(bool disposing)
     {
        // Be sure we have not already been disposed!
        if (!this.disposed)
        {
             // If disposing equals true i.e. if disposed explicitly, dispose all 
             // managed resources.
            if (disposing)
            {
             // Dispose managed resources.
            }
             // Clean up unmanaged resources here.
        }
        disposed = true;
      }

      // the below is called the destructor or Finalizer
     ~ClassWithDisposeAndFinalize()
     {
        // Call our shared helper method.
        // Specifying "false" signifies that the GC triggered the cleanup.
        CleanUp(false);
     }

2

Лучший пример, который я знаю.

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

2

Различаются методы Finalize и Dispose в C #.

GC вызывает метод finalize, чтобы вернуть неуправляемые ресурсы (такие как управление файлами, windows api, сетевое подключение, подключение к базе данных), но время не фиксируется, когда GC вызывает его. Он неявно вызывается GC, это означает, что у нас нет низкого уровня контроля над ним.

Метод Dispose: У нас есть низкий уровень контроля над ним, как мы его называем из кода. мы можем вернуть неуправляемые ресурсы всякий раз, когда считаем, что они непригодны для использования. Мы можем добиться этого путем внедрения шаблона IDisposal.


1

Экземпляры класса часто инкапсулируют контроль над ресурсами, которые не управляются средой выполнения, такими как дескрипторы окон (HWND), соединения с базой данных и так далее. Следовательно, вы должны предоставить как явный, так и неявный способ освобождения этих ресурсов. Обеспечьте неявное управление, реализовав защищенный метод Finalize для объекта (синтаксис деструктора в C # и управляемые расширения для C ++). Сборщик мусора вызывает этот метод в какой-то момент после того, как больше нет допустимых ссылок на объект. В некоторых случаях вы можете захотеть предоставить программистам, использующим объект, возможность явно освобождать эти внешние ресурсы до того, как сборщик мусора освободит объект. Если внешний ресурс является дефицитным или дорогим, можно добиться большей производительности, если программист явно освобождает ресурсы, когда они больше не используются. Чтобы обеспечить явное управление, реализуйте метод Dispose, предоставляемый интерфейсом IDisposable. Потребитель объекта должен вызывать этот метод, когда это делается с использованием объекта. Dispose может быть вызван, даже если другие ссылки на объект живы.

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


1

Основное различие между Dispose и Finalize заключается в том, что:

Disposeобычно вызывается вашим кодом. Ресурсы освобождаются мгновенно, когда вы звоните. Люди забывают вызывать метод, поэтому using() {}утверждение придумано. Когда ваша программа завершит выполнение кода внутри {}, она автоматически вызовет Disposeметод.

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

Я лично написал бы большую часть логики уничтожения в Dispose. Надеюсь, это прояснит путаницу.


-1

Как мы знаем, dispose и finalize используются для освобождения неуправляемых ресурсов ... но разница заключается в том, что finalize использует два цикла для освобождения ресурсов, тогда как для dispose используется один цикл ...


Утилизация освобождает ресурс немедленно. Финализация может или не может освободить ресурс с какой-либо степени своевременности.
суперкат

1
Ах, он, вероятно, означает, что «финализируемый объект должен быть обнаружен GC дважды, прежде чем его память будет восстановлена», подробнее читайте здесь: ericlippert.com/2015/05/18/…
aeroson

-4

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

Что касается второго вопроса, то лучше сначала прочитать это Правильное использование интерфейса IDisposable, который утверждает, что

Это твой выбор! Но выберите Dispose.

Другими словами: сборщик мусора знает только о финализаторе (если таковой имеется. Также известен как деструктор для Microsoft). Хороший код попытается очистить от обоих (финализатор и удаление).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.