Правильне використання інтерфейсу IDisposable


1657

Я знаю з читання документації Microsoft, що "первинне" використанняIDisposable інтерфейсу є очищення некерованих ресурсів.

Для мене "некерований" означає такі речі, як підключення до бази даних, сокети, ручки вікон тощо. Але я бачив код, де Dispose()метод реалізований для вільного керування ресурсів, що мені здається зайвим, оскільки збирач сміття повинен дбати що для вас.

Наприклад:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Моє запитання: чи робить це сміттєзбірник вільною пам'яттю, якою користується MyCollection швидше, ніж зазвичай?

редагувати : Поки люди розмістили кілька хороших прикладів використання IDisposable для очищення некерованих ресурсів, таких як підключення до бази даних та растрові карти. Але припустимо, що _theListу наведеному вище коді містився мільйон рядків, і ви хотіли звільнити цю пам'ять зараз , а не чекати сміттєзбірника. Чи зможе це зробити вищевказаний код?


34
Мені подобається прийнята відповідь, тому що вона вказує вам правильну схему використання IDisposable, але, як сказав ОП у своїй редакції, це не відповідає його наміченому питанню. IDisposable не "викликає" GC, він просто "позначає" об'єкт як зруйнований. Але який справжній спосіб звільнити пам'ять "прямо зараз", а не чекати, коли GC вступить? Я думаю, що це питання заслуговує на більшу дискусію.
Карайте Вору

40
IDisposableнічого не знаменує. DisposeМетод робить те , що він повинен зробити , щоб очистити ресурси , використовувані екземпляром. Це не має нічого спільного з GC.
Джон Сондерс

4
@John. Я розумію IDisposable. І саме тому я сказав, що прийнята відповідь не відповідає наміченому питанню ОП (та подальшій редагуванні) про те, чи допоможе IDisposable у <i> звільненні пам'яті </i>. Оскільки не IDisposableмає нічого спільного з звільненням пам’яті, а лише ресурсами, то, як ви вже говорили, взагалі не потрібно встановлювати керовані посилання на null, що робив ОП у своєму прикладі. Отже, правильна відповідь на його запитання: "Ні, це не допомагає вільній пам'яті швидше. Насправді, це зовсім не допомагає вільній пам'яті, лише ресурсам". Але все одно, дякую за ваш внесок.
Карайте Вору

9
@desigeek: якщо це так, то ви не мали б сказати, що "IDisposable не" викликає "GC, він просто" позначає "об'єкт як руйнівний"
Джон Сондерс

5
@desigeek: Не існує гарантованого способу детермінованого звільнення пам'яті. Ви можете зателефонувати GC.Collect (), але це ввічливий запит, а не попит. Щоб продовжити збирання сміття, всі діючі потоки повинні бути призупинені - прочитайте про концепцію безпечних точок .NET, якщо хочете дізнатися більше, наприклад, msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). асп . Якщо потік неможливо призупинити, наприклад, тому, що є виклик некерованого коду, GC.Collect () може взагалі нічого не робити.
Бетон Ганне

Відповіді:


2608

Сенс розпорядження - звільнити некеровані ресурси. Це потрібно зробити в якийсь момент, інакше вони ніколи не будуть прибиратися. Збирач сміття не знає, як викликати DeleteHandle()змінну типу IntPtr, він не знає, чи потрібно йому дзвонити DeleteHandle().

Примітка . Що таке некерований ресурс ? Якщо ви знайшли його в Microsoft .NET Framework: він керується. Якщо ви самі ходили колупати навколо MSDN, це некеровано. Все, що ви використовували P / Invoke дзвінки, щоб вийти за межі приємного світу зручності всього, що вам доступне в .NET Framework, не керовано - і тепер ви відповідаєте за його очищення.

Об'єкт, який ви створили, повинен розкрити якийсь метод, який може викликати зовнішній світ, щоб очистити некеровані ресурси. Метод можна назвати все, що завгодно:

public void Cleanup()

або

public void Shutdown()

Але замість цього є стандартизована назва цього методу:

public void Dispose()

Був навіть створений інтерфейс IDisposable, у якому є лише один метод:

public interface IDisposable
{
   void Dispose()
}

Таким чином, ви змушуєте ваш об'єкт відкривати IDisposableінтерфейс, і таким чином ви обіцяєте, що ви написали цей єдиний метод для очищення ваших некерованих ресурсів:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

І ви закінчили. За винятком, що ви можете зробити краще.


Що робити, якщо ваш об'єкт виділив 250MB System.Drawing.Bitmap (тобто клас. Bitmap, керований .NET) як якийсь буфер кадру? Звичайно, це керований об’єкт .NET, і збирач сміття звільнить його. Але чи справді ви хочете залишити 250 Мб пам'яті, просто сидячи там - чекаючи, коли збирач сміття згодом прийде і звільнить його? Що робити, якщо є відкрите підключення до бази даних ? Звичайно, ми не хочемо, щоб це з'єднання було відкритим, чекаючи, коли GC доопрацює об'єкт.

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

Тож тепер ми:

  • позбутися некерованих ресурсів (тому що ми маємо) та
  • позбутися керованих ресурсів (тому що ми хочемо бути корисними)

Тож давайте оновимо наш Dispose()метод, щоб позбутися цих керованих об'єктів:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

І все добре, крім того, що ти можеш зробити краще !


Що робити, якщо людина забула подзвонити Dispose()на ваш об’єкт? Тоді б вони просочилися якісь некеровані ресурси!

Примітка. Вони не просочуються керованими ресурсами, оскільки з часом сміттєзбірник запуститься на фоновому потоці та звільнить пам'ять, пов’язану з будь-якими невикористаними об'єктами. Це стосуватиметься вашого об'єкта та будь-яких керованих вами об’єктів (наприклад, Bitmapта DbConnection).

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

Примітка. Збір сміття з часом звільнить усі керовані об'єкти. Коли це робить, він викликаєFinalize метод на об'єкті. GC не знає, чи догляду, про вашому Dispose методу. Це було лише ім'я, яке ми вибрали для методу, який ми називаємо, коли хочемо позбутися некерованих речей.

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

Примітка. У C # ви явно не перекриваєте Finalize()метод. Ви пишете метод, схожий на деструктор C ++ , і компілятор вважає, що це ваша реалізація Finalize()методу:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

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

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Отже, що вам потрібно - це спосіб Finalize()розповістиDispose() що він не повинен торкатися жодних керованих ресурсів (бо їх може не бути там) ), при цьому вивільняючи некеровані ресурси.

Стандартний зразок для цього - мати Finalize()і Dispose()обидва виклику a третій (!) Метод; де ви передаєте булеву приказку, якщо ви її телефонуєте Dispose()(на відміну від Finalize()), це означає, що це безпечно для безкоштовних керованих ресурсів.

Цей внутрішній метод міг би бути даний якоюсь довільною назвою на кшталт "CoreDispose" або "MyInternalDispose", але традиція його називати Dispose(Boolean):

protected void Dispose(Boolean disposing)

Але більш корисною назвою параметра може бути:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

І ви змінюєте свою реалізацію IDisposable.Dispose()методу на:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

і ваш фіналізатор для:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Примітка : Якщо ваш об'єкт походить від об'єкта, який реалізує Dispose, тоді не забудьте зателефонувати до їх базового методу розпорядження, коли ви перекриєте Dispose:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

І все добре, крім того, що ти можеш зробити краще !


Якщо користувач зателефонував Dispose()на ваш об’єкт, то все очищено. Пізніше, коли збирач сміття підійде і зателефонує Finalize, він знову зателефонує Dispose.

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

Ви помітите в моєму коді, що я обережно видаляв посилання на об’єкти, які я розмістив, тому не намагаюся телефонувати Dispose на непотрібний об'єкт. Але це не завадило тонкому клопу повзати.

Коли користувач викликає Dispose(): ручка CursorFileBitmapIconServiceHandle знищена. Пізніше, коли працює сміттєзбірник, він знову спробує знищити ту саму ручку.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Як ви це вирішите, повідомте сміттєзбірнику, що йому не потрібно заважати доопрацьовувати об’єкт - його ресурси вже очищені, і більше роботи не потрібно. Ви робите це, зателефонувавши GC.SuppressFinalize()в Dispose()метод:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Тепер, коли користувач зателефонував Dispose(), ми маємо:

  • звільнили некеровані ресурси
  • звільнили керовані ресурси

В GC немає сенсу запускати фіналізатор - про все подбали.

Не можу я використовувати Finalize для очищення некерованих ресурсів?

Документація для Object.Finalize:

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

Але документація MSDN також говорить про IDisposable.Dispose:

Виконує завдання, визначені додатком, пов’язані із звільненням, звільненням або скиданням некерованих ресурсів.

Так що це? Яке з них є місцем для очищення некерованих ресурсів? Відповідь:

Це твій вибір! Але вибирай Dispose.

Ви, безумовно, можете розмістити свою некеровану очистку у фіналізаторі:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Проблема в тому, що ви не знаєте, коли збирач сміття обійдеться, щоб доопрацювати ваш об’єкт. Ваші некеровані, непотрібні, не використовувані рідні ресурси будуть триматися, поки сміттєзбірник врешті не запуститься. Тоді він зателефонує до вашого методу завершення; прибирання некерованих ресурсів. Документація Object.Finalize вказує на це:

Не визначений точний час, коли виконується фіналізатор. Щоб забезпечити детермінований випуск ресурсів для екземплярів вашого класу, застосуйте метод Close або забезпечте IDisposable.Disposeреалізацію.

Це чеснота використання Disposeдля очищення некерованих ресурсів; ви дізнаєтесь і контролюєте, коли очищений некерований ресурс. Їх знищення є "детермінованим" .


Щоб відповісти на своє первісне запитання: чому б не звільнити пам'ять зараз, а не тоді, коли GC вирішить це зробити? У мене є особи програмного забезпечення для розпізнавання , що потреби , щоб позбутися від 530 МБ внутрішніх образів в даний час , так як вони більше не потрібно. Коли ми цього не робимо: машина перемелюється до місця обміну.

Бонусне читання

Для всіх, кому подобається стиль цієї відповіді (пояснюючи чому , так як стає очевидним), пропоную прочитати першу главу Основного COM від Дон Бокса:

На 35 сторінках він пояснює проблеми використання бінарних об'єктів і вигадує COM перед вашими очима. Після того, як ви зрозумієте, чому саме COM, залишилися 300 сторінок очевидні, і лише детально описано впровадження Microsoft.

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

Додаткове бонусне читання

Коли Ерік Ліпперт усе, що ви знаєте, не так

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


12
Ви можете зробити краще - вам потрібно додати дзвінок до GC.SuppressFinalize () у Dispose.
плінтус

55
@Daniel Earwicker: Це правда. Корпорація Майкрософт хотіла б, щоб ви взагалі припинили використання Win32 і дотримувались вигадливих, портативних, незалежних пристроїв .NET Framework дзвінків. Якщо ви хочете піти навколо операційної системи внизу; тому що ти думаєш, що знаєш, на якій ОС працює: ти береш своє життя у свої руки. Не кожен додаток .NET працює в Windows або на робочому столі.
Ян Бойд

34
Це чудова відповідь, але я думаю, що все-таки виграє остаточний перелік коду для стандартного випадку та для випадку, коли клас походить від базового класу, який вже реалізує Dispose. наприклад, прочитавши тут ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ), а також я заплутався, що мені робити, виходячи з класу, який вже реалізує Dispose ( ей, я новачок у цьому).
integra753

5
@GregS та інші. Як правило, я б не переймався налаштуваннями посилань на null. По-перше, це означає, що ви не можете їх зробити readonly, по-друге, ви повинні робити дуже некрасиві !=nullперевірки (як у прикладі коду). Ви можете мати прапор disposed, але простіше не турбуватися про це. .NET GC є досить агресивним, що посилання на поле xбільше не вважатиметься "використаним" часом, коли він проходить x.Dispose()рядок.
porges

7
На другій сторінці згаданої вами книги Дон Бокса він використовує приклад реалізації O (1) алгоритму пошуку, "деталі якого залишаються як вправа для читача". Я сміявся.
витер

65

IDisposableчасто використовується для використання usingоператора та використання простого способу детермінованого очищення керованих об'єктів.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

6
Мені це подобається особисто, але це не дуже відповідає принципам дизайнерських вказівок.
mqp

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

Він не точно відповідає правилам, визначеним у FDG, але це, безумовно, правильне використання шаблону, як це потрібно для того, щоб використовувати його з використанням оператора.
Скотт Дорман

2
Поки Log.Outdent не кидає, в цьому точно немає нічого поганого.
Даніель Ервікер

1
Різні відповіді на питання Чи не зловживає використовувати IDisposable і «використовувати» як засіб для отримання «масштабної поведінки» для безпеки винятків? детальніше зупинимось на тому, чому різним людям подобається / не подобається ця техніка. Це дещо суперечливо.
Брайан

44

Метою схеми розпорядження є створення механізму очищення як керованих, так і некерованих ресурсів, і коли це відбувається, залежить від способу виклику методу розпорядження. У вашому прикладі використання Dispose фактично не робить нічого, пов'язаного з розпорядженням, оскільки очищення списку не впливає на те, що ця колекція розпоряджається. Аналогічно, виклики встановлення змінних на нуль також не впливають на GC.

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

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Тут найбільш важливим є метод Dispose (bool), який фактично працює за двох різних обставин:

  • dispose == true: метод був викликаний безпосередньо або опосередковано кодом користувача. Керовані та некеровані ресурси можуть бути розміщені.
  • dispose == false: метод був викликаний під час виконання зсередини фіналізатора, і ви не повинні посилатися на інші об'єкти. Утилізувати можуть лише некеровані ресурси.

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

Вся суть IDisposable і модель розпорядження не полягає в негайному звільненні пам'яті. Єдиний раз, коли виклик розпоряджатись, насправді навіть матиме можливість негайно звільнити пам'ять, коли він обробляє розміщений == помилковий сценарій та маніпулює некерованими ресурсами. Для керованого коду пам'ять насправді не буде відтворена, поки GC не запустить цикл збору, над яким ви дійсно не маєте контролю (крім виклику GC.Collect (), про який я вже згадував - це не дуже гарна ідея).

Ваш сценарій насправді не дійсний, оскільки рядки в .NET не використовують жодних безрегламентованих ресурсів і не реалізують IDisposable, немає ніякого способу змусити їх "очистити".


4
Ви не забули застосувати фіналізатор?
Будда

@Budda: Ні, він використовує SafeHandle. Не потрібно деструктора.
Хенк Холтерман

9
+1 для додавання мережі безпеки для декількох викликів до Dispose (). Спеціаліст каже, що багаторазові дзвінки повинні бути безпечними. Занадто багато класів Microsoft не реалізують цього, і ви отримуєте дратуючи ObjectDisposedException.
Джессі Чизгольм

5
Але Dispose (bool disiting) - це ваш власний метод у вашому класі SimpleCleanup, і він ніколи не буде викликаний рамкою. Оскільки ви називаєте це лише параметром "true" як параметр, "disposition" ніколи не буде помилковим. Ваш код дуже схожий на приклад MSDN для IDisposable, але йому не вистачає фіналізатора, як зазначав @Budda, звідки надходить дзвінок з dispose = false.
йойо

19

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

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

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

Але - і це головне - вони можуть бути будь-якою парою функцій. Один будує державу, інший розриває його. Якщо стан був побудований, але ще не зруйнований, то існує екземпляр ресурсу. Ви повинні домовитись про те, щоб терта відбулася в потрібний час - ресурсом не керує CLR. Єдиний автоматично керований тип ресурсу - це пам'ять. Існує два види: GC та stack. Типи значень керуються стеком (або приєднанням до внутрішніх еталонних типів), а типовими значеннями керуються GC.

Ці функції можуть спричинити зміни стану, які можуть бути вільно переплетені, або можуть бути ідеально вкладеними. Зміни стану можуть бути безпечними для потоків, а можуть і не бути.

Подивіться на приклад у питанні Справедливості. Зміни в відступі файлу журналу повинні бути ідеально вкладені, інакше все піде не так. Також вони навряд чи будуть безпечними нитками.

Можна очищати сміттєзбірник, щоб очистити ваші некеровані ресурси. Але лише в тому випадку, якщо функції зміни стану є безпечними для потоків і два стани можуть мати час життя, який перекривається будь-яким чином. Тож приклад правосуддя ресурсу НЕ повинен мати фіналізатор! Це просто нікому не допоможе.

Для таких видів ресурсів ви можете просто реалізувати IDisposable, без фіналізатора. Фіналізатор абсолютно необов’язковий - він повинен бути. Про це замальовано або навіть не згадується у багатьох книгах.

Тоді вам потрібно використовувати usingоператор, щоб мати будь-який шанс забезпечити Disposeвиклик. Це по суті, як підключення їзди зі стеком (так як фіналізатор до GC, usingце до стека).

Відсутня частина полягає в тому, що вам потрібно вручну написати Dispose і зробити його викликом на ваші поля та базовий клас. Програмістам на C ++ / CLI цього не потрібно робити. Компілятор пише їх для них у більшості випадків.

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

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

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

І тоді простим прикладом буде:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

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

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


re: "Більше не повинно бути викликів методів об'єкта після того, як на нього буде викликано Dispose". "Повинно" бути оперативним словом. Якщо у вас очікують асинхронні дії, вони можуть з’явитися після того, як ваш об’єкт буде утилізований. Викликаючи ObjectDisposedException.
Джессі Чизгольм

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

@supercat - якщо вас цікавить, я написав наступне повідомлення через пару днів після того, як я написав вищезгадану відповідь: smellegantcode.wordpress.com/2009/02/13/…
Daniel Earwicker

1
@DanielEarwicker: Цікава стаття, хоча я можу придумати хоча б один тип некерованого ресурсу, який ви дійсно не охоплюєте: підписки на події з довгоживучих об’єктів. Підписки на події є можливими, але навіть якщо пам'ять була необмеженою, їх не можна розпоряджатись. Наприклад, перелік для колекції, що дозволяє змінювати під час перерахування, може знадобитися підписатися на оновлення сповіщень про колекцію, і колекція може бути оновлена ​​багато разів протягом свого життя. Якщо нумератори покинуті без підписки ...
supercat

1
Пара операцій enterі exitє ядром того, як я думаю про ресурс. Підписка / скасування передплати на події повинна без проблем укладатись у цю програму. З точки зору ортогональних / мінливих характеристик, це практично не відрізняється від витоку пам'яті. (Це не дивно, оскільки підписка просто додає об’єкти до списку.)
Daniel Earwicker

17

Я використовую сценарії IDisposable: очищення некерованих ресурсів, підписка на події, тісні зв’язки

Ідіома, яку я використовую для впровадження IDisposable ( не є безпечним потоком ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

Повне пояснення шаблону можна знайти на сайті msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ

3
ніколи не повинен включати фіналізатор, якщо у вас немає керованих ресурсів. Вже тоді кращою реалізацією є загортання керованого ресурсу в SafeHandle.
Дейв Блек

11

Так, цей код є абсолютно зайвим і непотрібним, і він не змушує сміттєзбірника робити нічого, чого б інакше не робив (як тільки екземпляр MyCollection вийде за межі сфери, тобто). Особливо це .Clear()дзвінки.

Відповідь на вашу редагування: Сортування. Якщо я це роблю:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Це функціонально ідентично цьому з метою управління пам'яттю:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Якщо вам справді дуже потрібно звільнити пам'ять у цю мить, телефонуйте GC.Collect(). Тут немає жодних причин робити це. Пам'ять буде звільнена, коли вона знадобиться.


2
re: "Пам'ять буде звільнена, коли це буде потрібно". Скоріше скажіть, "коли GC вирішить, що це потрібно". Можливо, ви побачите проблеми з системою, перш ніж GC вирішить, що пам'ять справді потрібна. Звільнення його зараз може не бути суттєвим, але може бути корисним.
Джессі Чизгольм

1
Існують деякі кутові випадки, коли опущення посилань у колекції може прискорити збирання сміття згаданих предметів. Наприклад, якщо великий масив створений і заповнений посиланнями на менші новостворені елементи, але це не потрібно дуже довго після цього, відмова від масиву може призвести до збереження цих елементів до наступного рівня GC рівня 2, в той час як це нульове спочатку може зробити пункти придатними для наступного рівня 0 або рівня 1 GC. Безумовно, мати великі недовговічні об’єкти на Великій Купі об’єктів все одно прискіпливо (мені не подобається дизайн), але ...
supercat

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

11

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

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

EDIT У відповідь на коментар Скотта:

Єдиний вплив на показники ефективності GC - це виклик [sic] GC.Collect () "

Концептуально GC підтримує вигляд опорного графіка об'єкта та всіх посилань на нього із стекових кадрів потоків. Ця купа може бути досить великою і охоплює багато сторінок пам'яті. В якості оптимізації GC зберігає аналіз своїх сторінок, які навряд чи змінюватимуться дуже часто, щоб уникнути зайвого перегляду сторінки. GC отримує сповіщення від ядра, коли дані на сторінці змінюються, тому він знає, що сторінка брудна і вимагає повторного сканування. Якщо колекція знаходиться в Gen0, то ймовірно, що інші речі на сторінці теж змінюються, але це менш вірогідно в Gen1 та Gen2. Анекдотично, ці гачки не були доступні в Mac OS X для команди, яка перенесла GC на Mac, щоб плагін Silverlight працював на цій платформі.

Ще один момент проти зайвого розпорядження ресурсами: уявіть ситуацію, коли процес розвантажується. Уявіть також, що процес триває певний час. Цілком ймовірно, що багато сторінок пам'яті цього процесу були замінені на диск. Принаймні, їх більше немає в кеші L1 або L2. У такій ситуації немає жодної сенси для програми, яка розвантажує обмін усіма даними та кодовими сторінками назад у пам’ять, щоб “звільнити” ресурси, які операційно буде видана оперативною системою у будь-якому разі після завершення процесу. Це стосується керованих і навіть певних некерованих ресурсів. Потрібно утилізувати лише ресурси, які зберігають не фонові потоки, інакше процес залишиться живим.

Тепер, під час звичайного виконання, є ефемерні ресурси, які потрібно правильно очистити (як @fezmonkey вказує на підключення до бази даних, розетки, ручки вікон ), щоб уникнути некерованих витоків пам'яті. Це види речей, які потрібно утилізувати. Якщо ви створюєте якийсь клас, який володіє потоком (а маючи на увазі, я маю на увазі, що він його створив, і тому відповідальний за те, щоб він зупинився, принаймні за моїм стилем кодування), то цей клас, швидше за все, повинен впроваджувати IDisposableта руйнувати нитку під час Dispose.

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


Виклик розпорядження цілком дійсний, законний та заохочується. Об'єкти, які реалізують IDisposable, зазвичай роблять це з причини. Єдиний вплив на показники ефективності GC - це виклик GC.Collect ().
Скотт Дорман

Для багатьох класів .net утилізація є "дещо" необов'язковою, це означає, що відмова від екземплярів "зазвичай" не спричинить жодних проблем, доки не сходить з розуму, створюючи нові екземпляри та відмовляючись від них. Наприклад, згенерований компілятором код для елементів керування, здається, створює шрифти, коли елементи керування створюються миттєво, і відмовляється від них, коли форми розміщені; якщо один створює і розпоряджається тисячами елементів керування, це може пов'язати тисячі ручок GDI, але в більшості випадків елементи керування не створюються та знищуються настільки. Тим не менш, слід все ж намагатися уникати такої відмови.
supercat

1
Що стосується шрифтів, я підозрюю, що проблема полягає в тому, що Microsoft ніколи не визначав, яка організація відповідає за розміщення об'єкта "шрифт", призначеного для управління; в деяких випадках елементи керування можуть використовувати шрифт із об’єктом, що триває довше, тому мати керування Dispose the font було б погано. В інших випадках шрифт буде присвоєно елементу управління і ніде більше, тому якщо елемент управління не розпоряджається ним, ніхто не буде. Між іншим, цієї складнощі з шрифтами можна було б уникнути, якби існував окремий не одноразовий клас FontTemplate, оскільки органи управління, схоже, не використовують ручку GDI свого шрифту.
supercat

Щодо теми необов’язкових Dispose()дзвінків, дивіться: stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

У прикладі, який ви опублікували, він досі не "звільняє пам'ять". Уся пам’ять збирається сміттям, але це може дозволяти пам'яті збирати в попередньому поколінні . Вам доведеться запустити кілька тестів, щоб бути впевненим.


Рамкові рекомендації щодо дизайну - це вказівки, а не правила. Вони розповідають, для чого в першу чергу призначений інтерфейс, коли ним користуватися, як ним користуватися, а коли не використовувати.

Одного разу я прочитав код, який був простим RollBack () при збої з використанням IDisposable. Клас MiniTx нижче перевірятиме прапор на Dispose (), і якщо Commitвиклик ніколи не відбувся, він зателефонував би Rollbackсобі. Це додало шар непрямості, що робить код виклику набагато простішим для розуміння та обслуговування. Результат виглядав приблизно так:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Я також бачив код часу / журналу робити те саме. У цьому випадку метод Dispose () зупинив таймер і зареєстрував, що блок вийшов.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Ось ось кілька конкретних прикладів, які не роблять жодної некерованої очистки ресурсів, але успішно використовуються IDisposable для створення більш чистого коду.


Погляньте на приклад @Daniel Earwicker, використовуючи функції вищого порядку. Для тестування, хронометражу, ведення журналів тощо. Це здається набагато простішим.
Алуан Хаддад


6

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

Громадський клас LargeStuff
  Реалізує IDisposable
  Private _Large як string ()

  'Якийсь дивний код, що означає _Large, містить кілька мільйонів довгих рядків.

  Public Sub Dispose () Реалізує IDisposable.Dispose
    _Великий = нічого
  Кінець Під

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

Відповідь: Ні.
Calling Dispose може вивільнити некеровані ресурси, вона НЕ МОЖНА відновлювати керовану пам’ять, лише GC може це зробити. Це не означає, що вищезгадане - це не дуже гарна ідея, дотримуючись вищезгаданого зразка - це все-таки хороша ідея. Після запуску Dispose нічого не зупиняє GC повторно вимагати пам'яті, яку використовував _Large, навіть якщо екземпляр LargeStuff все ще може бути в області застосування. Рядки в _Large також можуть бути в gen 0, але екземпляр LargeStuff може бути gen 2, тому знову пам'ять буде повторно заявлено.
Немає сенсу додавати фіналізатор для виклику методу Dispose, показаного вище. Це просто ЗАСТАВИТЬ повторне вимагання пам'яті, щоб дозволити фіналізатору працювати.


1
Якщо екземпляр LargeStuffбув достатньо довгий, щоб перейти до покоління 2, і якщо він _Largeмістить посилання на новостворену рядок, що знаходиться в поколінні 0, то, якщо екземпляр LargeStuffзалишено без нульового виведення _Large, то рядок, на який посилається _Largeзберігатиметься до наступної колекції Gen2. Нулювання _Largeможе дозволити усунути рядок у наступній колекції Gen0. У більшості випадків видалення посилань не є корисним, але є випадки, коли це може принести певну користь.
supercat

5

Крім свого основного застосування в якості способу контролювати термін служби в системних ресурсах (повністю покритий дивовижним відповіддю Яна , престижність!), То IDisposable / с використанням комбо також може бути використаний для визначення області зміни стану (критичних) глобальних ресурсів : консолі , то потоки , то процес , будь-який глобальний об'єкт як екземпляр додатку .

Я написав статтю про цю схему: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

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


4

Якщо що, я очікую, що код буде меншим ефективним, ніж при його відсутності.

Викликати методи Clear () непотрібні, і GC, ймовірно, не зробив би цього, якби Dispose цього не зробив ...


2

Є деякі дії, які Dispose()операція робить у прикладі коду, які можуть мати ефект, який не відбудеться через звичайний GCMyCollection об'єкта.

Якщо об'єкти, на які посилаються _theListабо _theDictпосилаються інші об'єкти, то це List<>абоDictionary<> об'єкт не підлягатиме колекції, але раптом не матиме вмісту. Якби не було операції Dispose (), як у прикладі, ці колекції все одно містили б їхній вміст.

Звичайно, якби це була ситуація, я би назвав це зламаною конструкцією - я просто вказую (педантично, гадаю), що Dispose()операція може бути не надмірною, залежно від того, чи є інші способи використання List<>чи Dictionary<>ні показаний на фрагменті.


Вони приватні поля, тому я думаю, що справедливо вважати, що ОП не дає посилань на них.
mqp

1) фрагмент коду - це лише приклад коду, тому я просто вказую, що може виникнути побічний ефект, який легко не помітити; 2) приватні поля часто є ціллю властивості / методу getter - можливо, занадто багато (getter / setters деякі люди вважають трохи анти-шаблоном).
Майкл Берр

2

Одна з проблем більшості дискусій про "некеровані ресурси" полягає в тому, що вони насправді не визначають термін, але, здається, мають на увазі, що це має щось спільне з некерованим кодом. Хоча це правда, що багато типів некерованих ресурсів взаємодіють з некерованим кодом, думка про некеровані ресурси в таких умовах не корисна.

Натомість слід визнати, що спільного для всіх керованих ресурсів: усі вони тягнуть за собою об'єкт, який вимагає від зовнішньої «речі» зробити щось від свого імені, на шкоду деяким іншим «речам», а інша організація погодиться на це, поки подальше повідомлення. Якби об'єкт був покинутий і зникнути безслідно, ніколи ніколи не скаже, що поза «річчю», що йому більше не потрібно змінювати свою поведінку від імені об'єкта, який більше не існує; отже, корисність речі буде назавжди зменшена.

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


Ну, IMO, визначення некерованого об'єкта зрозуміло; будь-який не GC-об'єкт .
Еоніл

1
@Eonil: Некерований об’єкт! = Некерований ресурс. Такі речі, як події, можуть бути реалізовані повністю за допомогою керованих об'єктів, але все ще становлять некеровані ресурси, оскільки - принаймні у випадку короткочасних об'єктів, що підписуються на події довгоживучих об'єктів - GC нічого не знає про те, як їх очистити. .
supercat


2

Перше визначення. Для мене некерований ресурс означає деякий клас, який реалізує інтерфейс ID, що використовується, або щось, створене з використанням дзвінків до dll. GC не знає, як поводитися з такими об'єктами. Якщо клас містить, наприклад, лише типи значень, я не вважаю цей клас класом з некерованими ресурсами. Для свого коду я дотримуюся наступних практик:

  1. Якщо створений мною клас використовує деякі некеровані ресурси, то це означає, що я також повинен реалізувати інтерфейс IDisposable, щоб очистити пам'ять.
  2. Очистіть предмети, як тільки я закінчив їх використання.
  3. У своєму розпорядженні методом розпорядження я повторюю всі члени класу, які можна використовувати, і називаю Dispose.
  4. У моєму способі розпорядження зателефонуйте GC.SuppressFinalize (це), щоб повідомити сміттєзбірника, що мій об’єкт уже очищений. Я це роблю, оскільки виклик GC - це дорога операція.
  5. В якості додаткової обережності я намагаюся зробити можливий виклик Dispose () кілька разів.
  6. Колись я додаю приватний член _disposed і перевіряю, чи виклики методу чи об’єкт очищали. І якщо це було очищено, то генеруйте ObjectDisposedException
    Наступний шаблон демонструє те, що я описав словами як зразок коду:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"Для мене некерований ресурс означає деякий клас, який реалізує інтерфейс ID, що використовується, або щось, створене з використанням дзвінків до dll." Отже, ви говорите, що будь-який тип, який is IDisposableсам по собі слід вважати некерованим ресурсом? Це не здається правильним. Крім того, якщо тип імпульсування - це чистий тип значення, ви, здається, припускаєте, що його не потрібно утилізувати. Це також здається неправильним.
Алуан Хаддад

Всі судять самі. Мені не подобається додавати до мого коду щось тільки заради додавання. Це означає, що якщо я додаю IDisposable, це означає, що я створив певний функціонал, яким GC не може керувати, або, мабуть, він не зможе правильно керувати своїм життям.
Юрій Залецький

2

Даний зразок коду не є хорошим прикладом для IDisposableвикористання. Очищення словника зазвичай не повинно переходити до Disposeметоду. Елементи зі словника будуть очищені та утилізовані, коли вони вийдуть із сфери застосування.IDisposableПотрібна реалізація, щоб звільнити деяку пам'ять / обробники, які не випускатимуть / звільнятимуться навіть після того, як вони вийдуть із сфери застосування.

Наступний приклад показує хороший приклад для ідентифікаційного шаблону з деяким кодом та коментарями.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

Найбільш виправданим випадком використання для розпорядження керованими ресурсами є підготовка ГК до повернення ресурсів, які інакше ніколи не будуть зібрані.

Прекрасним прикладом є кругові посилання.

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

Єдиний спосіб цього - це ручне розбиття кругових посилань, встановивши посилання Батьки на нуль для дітей.

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


4
Здебільшого GC працює не шляхом виявлення мертвих об'єктів, а скоріше шляхом ідентифікації живих. Після кожного циклу gc, для кожного об'єкта, який зареєстрований для остаточного завершення, зберігається на великій купі об'єктів, або є ціллю в реальному часі WeakReference, система перевірятиме прапор, який вказує на те, що в останньому циклі GC було знайдено вкорінене посилання , або додасть об'єкт до черги об'єктів, які потребують негайної доопрацювання, звільнить об'єкт з великої маси об’єктів, або визнає недійсним слабке посилання. Кругові рефлекси не дозволять зберегти живі об’єкти, якщо інших даних немає.
supercat

1

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

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You- About

За власне питання; Якщо ви використовуєте IDisposable для очищення керованих об'єктів, які займають багато пам'яті, коротка відповідь була б ні . Причина полягає в тому, що як тільки ви розпоряджаєтесь ідентифікаційною карткою, ви повинні випускати її поза сферою застосування. У цей момент будь-які посилання на дочірні об’єкти також виходять із сфери застосування та будуть зібрані.

Єдиним реальним винятком з цього буде, якщо у вас багато пам'яті, пов'язаної в керованих об'єктах, і ви заблокували цей потік, очікуючи завершення певної операції. Якщо ті об’єкти, де після цього виклику не знадобиться, то встановлення цих посилань на null може дозволити сміттєзбірнику швидше збирати їх. Але цей сценарій представляв би поганий код, який потрібно було відновити, а не випадок використання IDisposable.


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