Фіналізувати проти розпорядження


215

Чому деякі користуються Finalizeметодом над Disposeметодом?

У яких ситуаціях ви б використовували Finalizeметод над Disposeметодом і навпаки?


Відповіді:


121

Інші вже покрили різницю між Disposeі Finalize(btw Finalizeметод все ще називається деструктором у специфікації мови), тому я просто додам трохи про сценарії, де цей Finalizeметод стане у нагоді.

Деякі типи інкапсулюють одноразові ресурси таким чином, щоб їх було легко використовувати та розпоряджатись однією дією. Загальне використання часто таке: відкривати, читати чи писати, закривати (розпоряджатися). Він дуже добре вписується в usingконструкцію.

Іншим трохи складніше. WaitEventHandlesдля примірників не використовуються так, як вони використовуються для передачі сигналу від однієї нитки до іншої. Тоді стає питанням, хто повинен закликати Disposeїх? Як такі захисні типи, як ці реалізують Finalizeметод, який гарантує розміщення ресурсів, коли додаток більше не посилається на додаток.


60
Я не міг зрозуміти цю схвалену відповідь. Я все ще хочу знати інше. Що це?
Ісмаїл

22
@Ismael: Найбільша ситуація, коли це Finalizeможе бути виправдано, це коли існує ряд об'єктів, які зацікавлені у збереженні ресурсу в живих, але немає способу, за допомогою якого об'єкт, який перестає цікавитись ресурсом, може з'ясувати, чи це останній. У такому випадку, Finalizeяк правило, стрілятимуть лише тоді, коли ніхто не зацікавиться об’єктом. Невичерпний термін роботи Finalizeжахливий для негорючих ресурсів, таких як файли та блокування, але може бути гаразд для змінних ресурсів.
supercat

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

5
@JonCoombs: Це майже все правильно, хоча, можливо, варто відзначити, що термін "ресурс, що зміщується" застосовується до речей, які є вільнозамінними до тих пір, поки вони не будуть придбані та знову стануть вільнозамінними після звільнення або залишення . Якщо в системі є пул об'єктів блокування і код набуває той, який він асоціюється з якоюсь сутністю, то поки хтось вважає, що посилання на цей замок з метою асоціації його з цією сутністю , цей замок не може бути замінений на будь-який інший. Якщо весь код, який піклується про об'єкт, що охороняється, покидає замок, але ...
supercat

... тоді він знову стане вільнозамінним до тих пір, поки він не асоціюється з якоюсь іншою сутністю.
supercat

135

Метод фіналізатора називається, коли ваш об’єкт збирається сміттям, і ви не маєте гарантії, коли це станеться (ви можете змусити його, але це зашкодить продуктивності).

DisposeМетод з іншого боку, призначений для виклику коду , який створив свій клас , так що ви можете очистити і звільнити ресурси , ви придбали (некеровані дані, з'єднання баз даних, файлові дескриптори, і т.д.) в той момент , код виконується з ваш об’єкт.

Стандартна практика полягає в тому, щоб реалізовувати IDisposableі Disposeтаким чином, щоб ви могли використовувати ваш об'єкт у складі using. Такі як using(var foo = new MyObject()) { }. А у вашому фіналізаторі ви зателефонуєте Dispose, на всякий випадок, якщо код, що дзвонить, забув розпоряджатися вами.


17
Вам потрібно бути обережним щодо виклику розпоряджатися від вашої реалізації Finalize - Dispose також може розпоряджатися керованими ресурсами, які ви не хочете торкатись у фіналізаторі, оскільки вони, можливо, вже були доопрацьовані.
itowlson

6
@itowlson: Перевірка нуля в поєднанні з припущенням, що об'єкти можна утилізувати двічі (при другому виклику нічого не робити) має бути досить хорошим.
Самуїл

7
Стандартна схема IDisposed та прихована реалізація Dispose (bool) для обробки розпорядження керованими компонентами, необов'язково, задовольняє цю проблему.
Броди

Здається, що немає причин реалізовувати деструктор (метод ~ MyClass ()), а завжди завжди реалізовувати та викликати метод Dispose (). Або я помиляюся? Чи міг би хтось надати мені приклад, коли потрібно реалізувати обидва?
dpelisek

66

Фіналізація - це метод зворотного зупинки, який викликається сміттєзбірником, коли він відновлює об'єкт. Розпорядження - це метод "детермінованої очищення", який закликається програмами звільняти цінні внутрішні ресурси (ручки вікон, підключення до бази даних тощо), коли вони більше не потрібні, а не залишати їх утримувати на невизначений термін, поки GC не обійдеться до об'єкта.

Як користувач об'єкта, ви завжди використовуєте Dispose. Фіналізація призначена для GC.

Як реалізатор класу, якщо ви тримаєте керовані ресурси, які слід розпоряджатися, ви реалізуєте Dispose. Якщо ви володієте власними ресурсами, ви реалізуєте і розпоряджатись, і доопрацьовувати, і обидва викликаєте загальний метод, який вивільняє власні ресурси. Ці ідіоми, як правило, поєднуються за допомогою приватного методу Dispose (bool disposition), який розпоряджається викликами з істинним та фіналізує виклики з помилковими. Цей метод завжди звільняє власні ресурси, потім перевіряє параметр розпорядження, і якщо він правдивий, він розпоряджається керованими ресурсами та викликає GC.SuppressFinalize.



2
Оригінальний рекомендований зразок для занять, в яких було поєднано ресурси для самоочищення ("керованого") та не самоочисних ("некерованих") ресурсів, давно застаріло. Кращий зразок - окремо загортати кожен некерований ресурс у власний керований об’єкт, який не містить чітких посилань на що-небудь, що не потрібно для його очищення. Все, на що об'єкт, що може допрацьовуватись, має пряме або непряме посилання, буде продовжено життя GC. Інкапсуляція речей, необхідних для очищення, дозволить уникнути продовження терміну служби GC речей, які не є.
supercat

2
@JCoombs: Disposeце добре, і правильно його реалізувати, як правило, просто. Finalizeце зло, і правильно його реалізувати, як правило, важко. Крім усього іншого, оскільки GC гарантуватиме, що особистість жодного об'єкта не буде "перероблена", поки існує будь-яка посилання на цей об'єкт, легко очистити купу Disposableоб'єктів, деякі з яких, можливо, вже були очищені, це нема проблем; будь-яке посилання на об'єкт, на який Disposeвже було викликано, залишатиметься посиланням на об'єкт, на який Disposeвже було викликано.
supercat

2
@JCoombs: Некеровані ресурси, навпаки, не мають такої гарантії. Якщо об'єкту Fredналежить ручка файлу № 42 і закриває його, система може привласнити такий самий номер до деякої обробки файлів, яка надається якомусь іншому об'єкту. У такому випадку ручка файлу №42 стосуватиметься не закритого файлу Фреда, а файла, який активно використовувався іншим об'єктом; бо Fredспробувати закрити ручку №42 знову було б згубно. Намагаючись на 100% надійно відслідковувати, чи ще не керований об’єкт ще випущений, є працездатним. Намагатися відслідковувати кілька об’єктів набагато складніше.
supercat

2
@JCoombs: Якщо кожен некерований ресурс розміщується у власному об'єкті обгортки, який не робить нічого, крім керує його життям, тоді зовнішній код, який не знає, чи був випущений ресурс, але знає, що він повинен бути, якщо він ще не був , можна сміливо попросити об’єкт обгортки відпустити його; об’єкт обгортки буде знати, чи зробив він це, і може виконати або проігнорувати запит. Те, що GC гарантує, що посилання на обгортку завжди буде дійсним посиланням на обгортку, є дуже корисною гарантією.
supercat

43

Довершити

  • Фіналізатори завжди повинні бути protected, ні, publicабо privateтак, щоб метод не можна було викликати безпосередньо з коду програми, і в той же час, він може робити виклик base.Finalizeметоду
  • Фіналізатори повинні випускати лише некеровані ресурси.
  • Рамка не гарантує, що фіналізатор буде виконаний у будь-якому випадку.
  • Ніколи не виділяйте пам'ять у фіналізаторів і не викликайте віртуальних методів від фіналізаторів.
  • Уникайте синхронізації та збільшення необроблених винятків у фіналізаторах.
  • Порядок виконання фіналізаторів є недетермінованим - іншими словами, ви не можете розраховувати на те, що інший об'єкт все ще доступний у вашому фіналізаторі.
  • Не визначайте фіналізатори для типів значень.
  • Не створюйте порожніх руйнівників. Іншими словами, ви ніколи не повинні чітко визначати деструктора, якщо ваш клас не потребує очищення некерованих ресурсів, і якщо ви їх визначите, він повинен виконати певну роботу. Якщо пізніше вам більше не потрібно буде очищати некеровані ресурси в деструкторі, видаліть його зовсім.

Утилізуйте

  • Використовуйте IDisposableдля кожного типу, який має фіналізатор
  • Переконайтесь, що об’єкт стає непридатним після виклику Disposeметоду. Іншими словами, уникайте використання об'єкта після Disposeвиклику методу на ньому.
  • Коли ви закінчите з ними, зателефонуйте Disposeдо всіх IDisposableтипів
  • Дозволено Disposeдзвонити кілька разів без помилок.
  • Пригнічуйте пізніші виклики до фіналізатора зсередини Disposeметоду, використовуючи GC.SuppressFinalizeметод
  • Уникайте створення типів одноразових значень
  • Уникайте викидів винятків із Disposeметодів

Утилізація / доопрацьована модель

  • Корпорація Майкрософт рекомендує реалізовувати як Disposeі Finalizeпід час роботи з некерованими ресурсами. FinalizeРеалізація буде працювати і ресурси будуть по- , як і раніше будуть випущені , коли об'єкт сміття , навіть якщо розробник забув викликати Disposeметод явно.
  • Очищення некерованих ресурсів у Finalizeметоді, а також Disposeметоді. Додатково викличте Disposeметод для будь-яких об’єктів .NET, які у вас є компонентами всередині цього класу (маючи некеровані ресурси як їх член) з Disposeметоду.

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

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

@tarik Я вже це навчився. У мене були "обіцянки" зачаття, коли я запитав це.
Ісмаїл

31

Завершити виклик GC, коли цей об'єкт більше не використовується.

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

Якщо користувач забув зателефонувати Dispose і якщо у класі впроваджено Finalize, GC переконається, що він викликається.


3
Найясніша відповідь колись
dariogriffo

19

У книжковій книжці MCSD Certification Toolkit (іспит 70-483) pag 193 є декілька ключів:

destructor ≈ (це майже дорівнює)base.Finalize() , Destructor перетворюється на версію заміщення методу Finalize, що виконує код деструктора, а потім викликає метод Finalize базового класу. Тоді його абсолютно не детермінований ви не можете знати, коли буде викликано, оскільки залежить від GC.

Якщо клас не містить керованих ресурсів і некерованих ресурсів , він не повинен реалізовувати IDisposableабо мати деструктор.

Якщо клас має лише керовані ресурси , він повинен реалізовуватись, IDisposableале він не повинен мати деструктора. (Коли деструктор виконується, ви не можете бути впевнені, що керовані об'єкти все ще існують, тому ви не можете викликати їхні Dispose()методи.)

Якщо клас має лише некеровані ресурси , він повинен реалізовуватись IDisposableта потребує деструктора у випадку, якщо програма не викликає 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 (який перекриває деструктор).
Ewa

7

У 99% часу ви не повинні турбуватися ні про один. :) Але, якщо ваші об'єкти містять посилання на не керовані ресурси (наприклад, ручки вікон, ручки файлів), вам потрібно надати спосіб керованому об'єкту звільнити ці ресурси. Фіналізація дає неявний контроль над вивільненням ресурсів. Його називає сміттєзбірник. Розпорядження - це спосіб явного контролю над вивільненням ресурсів, і його можна викликати безпосередньо.

Про предмет збору сміття можна дізнатися набагато більше , але це початок.


5
Я впевнений, що більше 1% додатків C # використовують бази даних: там, де вам доведеться турбуватися про IDSposable SQL.
Семюель

1
Крім того, ви повинні реалізувати IDisposable, якщо інкапсулює IDisposables. Що, ймовірно, покриває інші 1%.
Даррен Кларк

@Samuel: Я не бачу, які стосунки з цим мають. Якщо ви говорите про закриття з'єднань, це нормально, але це інша справа. Не потрібно своєчасно розпоряджатися об'єктами, щоб закрити з'єднання.
JP Alioto

1
@JP: Але використання (...) шаблону робить це набагато простіше впоратися.
Броди

2
Домовились, але саме в цьому справа. Використовуваний шаблон приховує виклик розпорядження для вас.
JP Alioto

6

Фіналізатор призначений для неявного очищення - ви повинні використовувати це щоразу, коли клас управляє ресурсами, які абсолютно повинні бути очищені, оскільки в іншому випадку ви просочилися б ручками / пам'яттю тощо ...

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

IDisposableІнтерфейс для явної очищення і набагато частіше використовується - ви повинні використовувати це , щоб дозволити користувачам явно звільнити або очищення ресурсів , коли вони закінчили з використанням об'єкта.

Зауважте, що якщо у вас є фіналізатор, ви також повинні реалізувати IDisposableінтерфейс, щоб дозволити користувачам явно звільнити ці ресурси раніше, ніж це було б, якби об’єкт збирав сміття.

Дивіться у розділі Оновлення ГД: Розпорядження, доопрацювання та управління ресурсами для того, що я вважаю найкращим та найповнішим набором рекомендацій щодо фіналізаторів та 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

Різниця між методами доопрацювання та розпорядження у C #.

GC закликає метод доопрацювання для відшкодування некерованих ресурсів (таких як операція файлів, windows api, підключення до мережі, підключення до бази даних), але час не визначається, коли GC називатиме його. Це називається неявно GC, це означає, що ми не маємо на ньому низького рівня контролю.

Спосіб утилізації: ми маємо на ньому низький рівень контролю, як називаємо це з коду. ми можемо відшкодувати некеровані ресурси, коли нам здається, що вони не є корисними. Ми можемо досягти цього, застосовуючи схему ідентифікації.


1

Екземпляри класу часто інкапсулюють контроль над ресурсами, якими не керує час виконання, наприклад ручками вікон (HWND), підключеннями до бази даних тощо. Тому ви повинні надати як явний, так і неявний спосіб звільнення цих ресурсів. Забезпечити неявний контроль, реалізуючи захищений метод Finalize на об'єкті (синтаксис деструктора в C # та керовані розширення для C ++). Колекціонер сміття викликає цей метод в якийсь момент після того, як більше немає дійсних посилань на об'єкт. У деяких випадках, можливо, ви хочете надати програмістам, що використовують об'єкт, можливість явно звільнити ці зовнішні ресурси, перш ніж збирач сміття звільнить об'єкт. Якщо зовнішній ресурс обмежений або дорогий, можна досягти кращої продуктивності, якщо програміст явно звільнить ресурси, коли вони більше не використовуються. Щоб забезпечити явний контроль, реалізуйте метод Dispose, передбачений інтерфейсом IDisposable. Споживач об'єкта повинен викликати цей метод, коли це робиться за допомогою об'єкта. Утилізацію можна назвати, навіть якщо інші посилання на об'єкт живі.

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


1

Основна відмінність між розпорядженням та фіналізацією полягає в тому, що:

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

Finalizeне називається вашим кодом. Це означає, щоб його викликали сміттєзбірник (GC). Це означає, що ресурс може бути звільнений у будь-який час у майбутньому, коли GC вирішить це зробити. Коли GC зробить свою роботу, вона пройде через багато методів Finalize. Якщо у вас є важка логіка в цьому, це зробить процес повільним. Це може спричинити проблеми з продуктивністю для вашої програми. Тому будьте уважні до того, що ви туди помістите.

Я особисто написав би велику частину логіки знищення в Dispose. Сподіваємось, це очистить плутанину.


-1

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


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

1
Ах, він, ймовірно, означає, що цей "об'єкт, що підлягає завершенню, повинен бути виявлений GC двічі, перш ніж його пам'ять буде відтворена", читайте докладніше тут: ericlippert.com/2015/05/18/…
aeroson

-4

Щоб відповісти на першу частину, ви повинні навести приклади, коли люди використовують різний підхід для абсолютно одного класу-об’єкта. Інакше важко (або навіть дивно) відповісти.

Щодо другого питання, краще прочитайте спочатку це Правильне використання інтерфейсу IDisposable, який стверджує це

Це твій вибір! Але виберіть Розпорядження.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.