Використання методу Finalize / Dispose у C #


381

C # 2008

Я над цим працюю вже деякий час, і я все ще плутаюсь щодо використання методів доопрацювання та розпорядження кодом. Мої запитання нижче:

  1. Я знаю, що нам потрібен лише фіналізатор під час розміщення некерованих ресурсів. Однак якщо є керовані ресурси, які здійснюють дзвінки на некеровані ресурси, чи все-таки потрібно буде застосувати фіналізатор?

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

    Чи можливо реалізувати IDisposable просто для того, щоб клієнти вашого класу могли використовувати оператор, що використовує?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Нижче я розробив цей простий код, щоб продемонструвати завершення використання:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }

Питання про вихідний код:

  1. Тут я не додав фіналізатор, і зазвичай фіналізатор викличе GC, а фіналізатор викличе Dispose. Оскільки у мене немає фіналізатора, коли я зателефоную за методом Dispose? Чи повинен клієнт класу викликати його?

    Тож мій клас у прикладі називається NoGateway, і клієнт може використовувати та розпоряджатися таким класом:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

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

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Я використовую WebClientклас у своєму NoGatewayкласі. Оскільки WebClientреалізує IDisposableінтерфейс, чи означає це, що WebClientопосередковано використовуються некеровані ресурси? Чи існує чітке і швидке правило для цього? Як я можу знати, що клас використовує некеровані ресурси?


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

Відповіді:


422

Рекомендована IDisposable картина тут . При програмуванні класу, який використовує IDisposable, як правило, слід використовувати два шаблони:

Реалізуючи герметичний клас, який не використовує керовані ресурси, ви просто реалізуєте метод Dispose, як і у звичайних реалізаціях інтерфейсу:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Реалізуючи незапечатаний клас, зробіть це так:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Зауважте, що я не оголосив фіналізатор у B; Вам слід застосувати фіналізатор лише у тому випадку, якщо у вас є фактичні некеровані ресурси для розпорядження. CLR має відношення до об'єктів, що можна доопрацювати, по-різному до об'єктів, що не завершуються, навіть якщо SuppressFinalizeвони викликаються.

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

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

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

Коли клас реалізує інтерфейс IDisposable, це означає, що десь є деякі некеровані ресурси, яких слід позбутися, коли ви закінчили використовувати клас. Фактичні ресурси вкладені в класи; видаляти їх не потрібно. Просто зателефонувавши Dispose()або загорнувши клас у засвідчення, ви using(...) {}зможете позбутися будь-яких некерованих ресурсів у міру необхідності.


26
Я згоден з thecoop. Зауважте, що вам не потрібен фіналізатор, якщо ви маєте справу лише з керованими ресурсами (насправді, НЕ слід намагатися отримувати доступ до керованих об'єктів зсередини вашого фіналізатора (крім "цього"), оскільки немає гарантованого порядку, у якому GC буде очищати об'єкти. Також, якщо ви використовуєте .Net 2.0 або вище, ви можете (і повинні) використовувати SafeHandles для обгортання некерованих ручок. Safehandles значно зменшують ваші потреби писати фіналізатори для керованих класів взагалі. Blogs.msdn. com / bclteam / архів / 2005/03/16 / 396900.aspx
JMarsch

5
Я думаю, що краще зателефонувати на MessageBox.Show ("Помилка" + GetType (). Ім'я + "не розміщено") у фіналізаторі, оскільки одноразовий об'єкт ВЖЕ ВИНАЄТЬ бути утилізований, і якщо ви цього не зробите, це найкраще попередити про цей факт якомога раніше.
erikkallen

95
@erikkallen це жарт? :)
Алекс Норкліфф

2
оскільки додаткові обчислювальні роботи потрібні в CLR для відстеження занять з активними фіналізаторами. - Реалізація фіналізатора спричиняє це. Виклик GC.SuppressFinalize означає, що Finalizer не повинен викликатися під час виконання. Це все ще йде Gen2 незалежно. Не додайте фіналізатор, якщо ви не маєте справу з керованими ресурсами. Запечатані або незапечатані модифікатори класу для цього не мають значення.
Річ Мелтон

3
@Ritch: цитування? Це не обов'язково погано; якщо ви реалізуєте IDisposable, швидше за все, він все одно зависне. Ви заощаджуєте CLR, намагаючись скопіювати його з Gen0 -> Gen1 -> Gen2
thecoop

123

Офіційну схему для реалізації IDisposableважко зрозуміти. Я вважаю, що це краще :

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Ще кращим рішенням є правило, згідно з яким завжди потрібно створити клас обгортки для будь-якого некерованого ресурсу, з яким потрібно обробляти:

public class NativeDisposable : IDisposable {

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

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

З SafeHandleта його похідними ці класи мають бути дуже рідкісними .

Результат для одноразових класів, які не займаються безпосередньо некерованими ресурсами, навіть за наявності спадщини, є потужним: їм більше не потрібно займатися некерованими ресурсами . Їх буде просто реалізувати та зрозуміти:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}

@Kyle: Дякую! Мені подобається це теж :-) Там в наступні до нього тут .
Йордау

4
Хоча одне, що я хочу зазначити, це не заважає називатися вдруге.
HuseyinUslu

5
@HuseyinUslu: це лише суть шаблону. Ви, звичайно, можете додати disposedпрапор і перевірити відповідно.
Йордао

2
@didibus: це проста справа додати disposedпрапор, перевірити його перед тим, як розпоряджатися, і встановити його після розміщення. Подивіться сюди на ідею. Ви також повинні перевірити прапор перед будь-якими методами класу. Має сенс? Це складно?
Йордао

1
+1 для "Ще кращим рішенням є правило, згідно з яким завжди потрібно створити клас обгортки для будь-якого некерованого ресурсу, з яким потрібно обробити" . Я наткнувся на це у додатку для VLC, і я його використовую з тих пір. Врятує стільки головних болів ...
Франц Б.

37

Зауважте, що будь-яка реалізація, яка використовується для ідентифікації, повинна відповідати наведеній нижче схемі (IMHO). Я розробив цю модель на основі інформації від кількох чудових .NET "богів" .NET Framework Design Guidelines (зауважте, що MSDN чомусь не дотримується цього!). Настанови .NET Framework Design були написані Кшиштофом Кваліною (архітектор CLR в той час), Бредом Абрамсом (я вважаю, менеджером програми CLR в той час) і Біллом Вагнером ([Ефективний C #] та [Більш ефективний C #] (просто візьміть a шукайте їх на Amazon.com:

Зауважте, що НІКОЛИ не слід застосовувати Finalizer, якщо ваш клас безпосередньо не містить (не успадковує) ресурси без управління. Після того, як ви реалізуєте Finalizer у класі, навіть якщо його ніколи не називають, гарантовано будете користуватися додатковою колекцією. Він автоматично розміщується у черзі на завершення (яка працює на одному потоці). Крім того, одна дуже важлива примітка ... весь код, виконаний у Finalizer (якщо вам потрібно реалізувати його), ОБОВ'ЯЗКОВО має бути безпечним для потоків І виключенням! BAD речі відбуватимуться інакше ... (тобто невизначена поведінка, а у випадку винятку - фатальна непоправна аварія програми).

Шаблон, який я склав (і написав фрагмент коду), наступний:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
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.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> 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.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Ось код для реалізації IDisposable у похідному класі. Зауважте, що вам не потрібно чітко перераховувати спадкування з IDisposable у визначенні похідного класу.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

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


Чи може хтось також додати шаблон для похідного класу (що походить з цього базового класу)
akjoshi

3
@akjoshi - я оновив вищевказаний зразок, щоб включити код для похідного одноразового класу. Також зверніть увагу: НІКОЛИ не застосовуйте Фіналайзер у похідному класі ...
Дейв Блек

3
Microsoft, здається, любить встановлювати прапор "утилізувати" наприкінці розпорядженого способу, але це здається мені неправильним. Надлишки дзвінків на "Розпорядження" не повинні робити нічого; хоча звичайно не можна очікувати, що Dispose буде викликатися рекурсивно, такі речі можуть статися, якщо хтось намагається розпоряджатись об'єктом, який залишився в недійсному стані, за винятком, який стався під час будівництва чи іншої операції. Я б подумав, що безпечне використання прапорця Interlocked.Exchangeз цілим числом IsDisposedу функції не-віртуальної обгортки.
supercat

@DaveBlack: Що робити, якщо ваш базовий клас не використовує керовані ресурси, але ваш похідний клас робить? Тоді вам доведеться реалізувати Finalizer у похідному класі? І якщо так, то як ви знаєте, що базовий клас його вже не реалізував, якщо у вас немає доступу до джерела?
Дідьє А.

@DaveBlack "Я розробив цю модель на основі інформації від кількох чудових .NET" богів "" Якщо одним з богів був Джон Скіт, то я буду дотримуватися ваших порад.
Елізабет

23

Я погоджуюся з pm100 (і я повинен був прямо сказати про це в своєму попередньому пості).

Ніколи не слід реалізовувати IDisposable у класі, якщо він вам не потрібен. Щоб бути дуже конкретним, існує приблизно 5 разів, коли вам коли-небудь знадобиться / слід реалізувати IDisposable:

  1. Ваш клас явно містить (тобто не через спадщину) будь-які керовані ресурси, які реалізують IDisposable і повинні бути очищені, як тільки ваш клас більше не використовується. Наприклад, якщо ваш клас містить екземпляр Stream, DbCommand, DataTable тощо.

  2. Ваш клас явно містить будь-які керовані ресурси, які реалізують метод Close () - наприклад, IDataReader, IDbConnection тощо. Зверніть увагу, що деякі з цих класів реалізують IDisposable, використовуючи метод Dispose (), а також метод Close ().

  3. Ваш клас явно містить некерований ресурс - наприклад, об’єкт COM, покажчики (так, ви можете використовувати вказівники в керованому C #, але вони повинні бути оголошені в блоках "небезпечних" і т.д. У випадку некерованих ресурсів, ви також повинні переконатися, що зателефонуйте System.Runtime.InteropServices.Marshal.ReleaseComObject () на RCW. Навіть незважаючи на те, що RCW теоретично є керованою обгорткою, під кришками все ще відбувається підрахунок посилань.

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

  5. Ваш клас містить будь-яку комбінацію перерахованих вище ...

Рекомендованою альтернативою для роботи з об’єктами COM та використання Marshal.ReleaseComObject () є використання класу System.Runtime.InteropServices.SafeHandle.

Команда BCL (колектив бібліотек базового класу) має тут хороший пост у блозі http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Одним з дуже важливих зауважень є те, що якщо ви працюєте з WCF та прибираєте ресурси, вам варто ВЖЕ ЗАВЖДИ уникати блоку "використання". Існує безліч публікацій в блогах, а деякі на MSDN про те, чому це погана ідея. Я також розмістив про це тут - не використовуйте "using ()" з проксі-сервером WCF


3
Я вважаю, що існує 5-й випадок: Якщо ваш клас підписується на події, використовуючи чіткі посилання, то вам слід застосувати IDisposable і відреєструватися від подій методу Dispose.
Дідьє А.

Привіт дидібус. Так, ви праві. Я забув про це. Я змінив свою відповідь, щоб включити її як випадок. Дякую.
Дейв Блек

Документація MSDN для шаблону розпорядження додає ще один випадок: "ВІДПОВІДУЮЧИ реалізуючи Базовий шаблон розпорядження для класів, які самі не містять керовані ресурси або одноразові об'єкти, але, ймовірно, мають підтипи, які це роблять. Чудовим прикладом цього є System.IO .Stream клас. Хоча це абстрактний базовий клас, який не містить ресурсів, більшість його підкласів це робить, і через це він реалізує цю схему ".
Gonen I

12

Використання лямбдів замість IDisposable.

Я ніколи не був в захваті від усієї ідеї, що використовує / IDisposable. Проблема полягає в тому, що він вимагає від абонента:

  • знайте, що вони повинні використовувати IDisposable
  • не забудьте використати "користуватися".

Мій новий кращий метод - замість цього використовувати фабричний метод та лямбда

Уявіть, що я хочу зробити щось із SqlConnection (щось, що повинно бути загорнене у користування). Класично ви б це зробили

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Новий шлях

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

У першому випадку абонент може просто не використовувати синтаксис, що використовує. У другому випадку у користувача немає вибору. Не існує методу, який створює об'єкт SqlConnection, абонент повинен викликати DoWithConnection.

DoWithConnection виглядає приблизно так

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection зараз приватний


2
Обгортання речей в лямбдах може бути хорошим підходом, але це має межі. Це не дуже погано для ситуацій, коли насправді всі споживачі класу використовували б "використовуючий" блок, але це забороняло б ситуації, коли метод зберігав би ідентифікатор у полі класу (безпосередньо, або в чомусь подібному до ітератора ).
supercat

@supercat Ви можете стверджувати, що забороняти зберігання речей, що вистрілюються, є доброю справою. Пропонована тут модель запозичень я змушує вас бути
схильними

Це може бути хорошою справою, але також може зробити дуже важкі операції дуже важкими. Наприклад, припустимо, що тип зчитувача баз даних замість реалізації IEnumerable <T> відкриває метод DoForAll(Action<T>) where T:IComparable<T>, викликаючи вказаного делегата на кожен запис. З огляду на два таких об'єкти, обидва з яких будуть повертати дані в упорядкованому порядку, як можна виводити всі елементи, що існують в одній колекції, а не в іншій? Якщо типи реалізовані IEnumerable<T>, можна було б виконати операцію злиття, але це не працюватиме DoForAll.
supercat

Єдиний спосіб, за яким я можу зробити об'єднання двох DoForAllколекцій без необхідності спочатку копіювати одну, в цілому, в якусь іншу структуру, - це використовувати два потоки, що було б скоріше грішним ресурсом, ніж просто використання пари IEnumerable's і будьте обережні звільнити їх.
supercat

-1: хороша відповідь на запитання, яке не було задано. Це було б чудовою відповіддю на те, як "зробити полегшення споживання об'єктів, що не використовуються"
Джон Сондерс,

10

ніхто не відповів на запитання про те, чи слід реалізовувати IDisposable, хоча і цього не потрібно.

Коротка відповідь: Ні

Довга відповідь:

Це дозволить споживачеві вашого класу використовувати "використання". Питання, яке я б задав, - чому вони це роблять? Більшість розробників не використовуватимуть "використання", якщо не знають, що вони повинні - і як вони знають. Або

  • його переглядає досвід (наприклад, клас сокета)
  • її документально підтверджено
  • вони обережні і бачать, що клас реалізує IDisposable

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

Ви реалізуєте Idisposable, щоб дозволити їм користуватися, але вони не будуть використовувати, якщо ви не скажете їм.

Тому не робіть цього


1
Я не розумію, чому розробник не використовує / використовує об'єкт, що реалізує IDisposable (якщо програма все одно не вийде).
Адріанм

1
справа в тому, що розробнику доведеться записати всі дзвінки, щоб розпоряджатися усіма кодовими шляхами, що призводять до нереференції. Так, наприклад, якщо я поміщаю екземпляр у словник, коли я видаляю записи зі словника, я повинен викликати розпорядження. У цьому випадку багато клопотів, які не потрібні - об’єкт не потрібно утилізувати
pm100

3
@ pm100 Re: Потрібно впроваджувати IDisposable - Є детальна стаття на codeproject.com/KB/dotnet/idisposable.aspx, в якій обговорюються деякі рідкісні випадки, коли ви можете задуматися над цим (дуже рідко, я впевнений). Коротше кажучи: якщо ви можете передбачити потребу в IDisposable у майбутньому або у похідному об'єкті, тоді ви можете подумати про те, щоб реалізувати IDisposable як "неоперативний" у вашому базовому класі, щоб уникнути проблем "зрізання", де потрібні деякі похідні об'єкти. утилізація, а інші - ні.
Кевін П. Райс

4
  1. Якщо ви використовуєте інші керовані об'єкти, які використовують некеровані ресурси, за їх остаточну відповідальність не входить відповідальність. Ваша відповідальність полягає в тому, щоб зателефонувати розпоряджатися цими об'єктами, коли виклик Dispose на ваш об'єкт, і він зупиняється на цьому.

  2. Якщо ваш клас не використовує ніяких обмежених ресурсів, я не розумію, чому ви зробили б ваш клас реалізувати IDisposable. Ви повинні робити це, лише якщо:

    • Знайте, що у вас скоро будуть обмежені ресурси у ваших об'єктах, тільки не зараз (і я маю на увазі, що як і в "ми все ще розвиваємося, це буде тут, перш ніж ми закінчимо", а не як "Я думаю, нам це буде потрібно ")
    • Використання обмежених ресурсів
  3. Так, код, який використовує ваш код, повинен викликати метод розпорядження вашого об'єкта. І так, код, який використовує ваш об’єкт, може використовуватись так, usingяк ви показали.

  4. (2 рази?) Цілком ймовірно, що WebClient використовує або некеровані ресурси, або інші керовані ресурси, що реалізують IDisposable. Точна причина, однак, не важлива. Що важливо, це те, що він реалізує IDisposable, і тому вам належить діяти на цих знаннях, видаляючи об'єкт, коли ви закінчите з ним, навіть якщо виявляється, що WebClient взагалі не використовує інших ресурсів.


4

Утилізація:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Приклад успадкування:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

4

Деякі аспекти іншої відповіді дещо неправильні з 2 причин:

Спочатку,

using(NoGateway objNoGateway = new NoGateway())

насправді еквівалентно:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Це може здатися смішним, оскільки "новий" оператор ніколи не повинен повертати "null", якщо у вас немає виключення OutOfMemory. Але врахуйте наступні випадки: 1. Ви викликаєте FactoryClass, який повертає ресурс, який не використовується, або 2. Якщо у вас є тип, який може або не може успадковувати з IDisposable, залежно від його реалізації - пам’ятайте, що я бачив, що шаблон, який використовується на відстані, реалізований неправильно багато Час у багатьох клієнтів, коли розробники просто додають метод Dispose (), не успадковуючи від IDisposable (поганий, поганий, поганий). У вас також може бути випадок повернення ідентифікаційного ресурсу з властивості чи методу (знову ж таки погано, погано, погано - не «видавати свої ідентифікаційні ресурси»)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Якщо оператор "as" повертає null (або властивість чи метод, що повертає ресурс), а ваш код у блоці "using" захищає від "null", ваш код не підірветься при спробі виклику Dispose на null об'єкт через нульова перевірка 'вбудований'.

Друга причина, що ваша відповідь не точна, полягає в наступному stmt:

Викликається фіналізатор, що GC знищує ваш об'єкт

По-перше, фіналізація (як і сама GC) не детермінована. CLR визначає, коли він викличе фіналізатор. тобто розробник / код поняття не має. Якщо шаблон IDisposable реалізований правильно (як я вже розміщував вище) і GC.SuppressFinalize () був викликаний, фіналізатор НЕ буде викликаний. Це одна з головних причин правильно реалізувати шаблон. Оскільки існує лише 1 потік фіналізатора на керований процес, незалежно від кількості логічних процесорів, ви можете легко погіршити продуктивність, створивши резервну копію або навіть повісивши нитку Finalizer, забувши викликати GC.SuppressFinalize ().

На своєму блозі я опублікував правильну реалізацію Шаблону розпорядження: Як правильно реалізувати шаблон розпорядження


2
Ви впевнені в написанні NoGateway = new NoGateway();і NoGateway != null?
Cœur

1
Це стосувалося stackoverflow.com/a/898856/3195477 ? Зараз немає відповіді на ім'я 'Icey'
UuDdLrLrSs

@DaveInCaz це здається, що це правильно. Я ніде не бачу "крижаних", але контекст моєї відповіді, здається, спрямований на відповідь, надану вашим посиланням вище. Може, він змінив своє ім’я користувача?
Дейв Блек

@DaveBlack круто, дякую. Я щойно відредагував це прямо в текст.
UuDdLrLrSs

2

1) WebClient - керований тип, тому вам не потрібен фіналізатор. Фіналізатор потрібен у тому випадку, якщо ваші користувачі не розпоряджаються () класом NoGateway, а рідний тип (який GC не збирає) потребує очищення після. У цьому випадку, якщо користувач не зателефонує Dispose (), вміщений WebClient буде видалений GC відразу після того, як це зробить NoGateway.

2) Опосередковано так, але вам не доведеться турбуватися про це. Ваш код правильний у вигляді підставки, і ви не можете перешкодити вашим користувачам забути розпоряджатися () дуже легко.


2

Візерунок від msdn

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}

1
using(NoGateway objNoGateway = new NoGateway())

еквівалентно

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Викликається фіналізатор, що GC знищує ваш об'єкт. Це може бути зовсім іншим часом, ніж тоді, коли ви покинете свій метод. Утилізація IDisposable викликається відразу після виходу із блоку використання. Отже, шаблон зазвичай використовується для використання для безкоштовного використання ресурсів відразу після того, як вони вам більше не потрібні.


1
Фіналізатор не закликає ГК знищувати об'єкт. Якщо "Довершити" буде відмінено, тоді, коли GC інакше знищив би об'єкт , він буде розміщений на черзі об'єктів, які потребують доопрацювання, тимчасово створивши на нього сильну посилання і - принаймні тимчасово - "воскресивши" його.
supercat

-5

З того, що я знаю, настійно не рекомендується використовувати Finalizer / Destructor:

public ~MyClass() {
  //dont use this
}

Переважно, це пов’язано з невідомим, коли або ЯКЩО це буде викликано. Спосіб утилізації набагато кращий, особливо якщо ви використовуєте або розпоряджаєтесь безпосередньо.

використання - це добре. використай це :)


2
Ви повинні перейти за посиланням у відповіді thecoop. Так, краще використовувати / Dispose, але клас одноразового використання обов'язково повинен реалізовувати і те, і інше.
Хенк Холтерман

Цікаво, що всі документи, які я читав від Microsoft - наприклад, керівні принципи щодо дизайну рамок - кажуть, що НІКОЛИ не використовуйте деструктор. Завжди використовуйте ідентифікатор.
Нік Мудрий

5
Просто розрізняйте використання класу та написання класу, прочитане їх ще раз.
Хенк Холтерман

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