Использование метода Finalize / Dispose в C #

381

C # 2008

Я работал над этим некоторое время, и я все еще не уверен в использовании методов finalize и dispose в коде. Мои вопросы ниже:

  1. Я знаю, что нам нужен только финализатор при утилизации неуправляемых ресурсов. Однако, если есть управляемые ресурсы, которые выполняют вызовы к неуправляемым ресурсам, нужно ли все же реализовать финализатор?

  2. Однако, если я разрабатываю класс, который не использует какой-либо неуправляемый ресурс - прямо или косвенно, должен ли я реализовать так, IDisposableчтобы клиенты этого класса могли использовать «оператор using»?

    Реально ли реализовать IDisposable просто для того, чтобы клиенты вашего класса могли использовать оператор using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Я разработал этот простой код ниже, чтобы продемонстрировать использование Finalize / dispose:

    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 автоматически вызываться, когда выполнение достигает конца блока using, или клиент должен вручную вызывать метод dispose? т.е.

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Я использую WebClientкласс в своем NoGatewayклассе. Поскольку WebClientреализует IDisposableинтерфейс, означает ли это, что WebClientкосвенно использует неуправляемые ресурсы? Есть ли жесткое и быстрое правило следовать этому? Как я узнаю, что класс использует неуправляемые ресурсы?

ant2009
источник
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а друзья не учитываются, поскольку они объявляют свои собственные финализаторы), то не реализуйте финализатор, поскольку сборщик мусора обрабатывает финализируемые классы по-разному, даже если позже вы подавите финализатор. Также обратите внимание, что, хотя у Bнего нет финализатора, он все равно вызывает, SuppressFinalizeчтобы правильно иметь дело с любыми подклассами, которые реализуют финализатор.

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

thecoop
источник
26
Я согласен с thecoop. Обратите внимание, что вам не нужен финализатор, если вы имеете дело только с управляемыми ресурсами (на самом деле, вы НЕ должны пытаться получить доступ к управляемым объектам из вашего финализатора (кроме «this»), поскольку не существует гарантированного порядка, в котором GC будет очищать объекты. Кроме того, если вы используете .Net 2.0 или выше, вы можете (и должны) использовать SafeHandles для обёртывания неуправляемых дескрипторов. Безопасные дескрипторы значительно снижают необходимость написания финализаторов для ваших управляемых классов вообще. Blogs.msdn. ru / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch
5
Я думаю, что лучше добавить вызов в MessageBox.Show («Ошибка» + GetType (). Name + «not disposed») в финализаторе, так как одноразовый объект должен ВСЕГДА удаляться, и если вам не удастся это сделать, это лучше всего быть предупрежденным к факту как можно раньше.
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
  }

}
Jordão
источник
@ Кайл: Спасибо! Мне нравится это тоже :-) Там в последующие к нему здесь .
Джордао
4
Хотя одна вещь, которую я хочу отметить, это не препятствует тому, чтобы быть вызванным во второй раз.
ХусейинУслу
5
@HuseyinUslu: это только суть паттерна. Вы можете, конечно, добавить disposedфлаг и проверить соответственно.
Джордао
2
@didibus: просто добавить disposedфлаг, проверить его перед утилизацией и установить после утилизации. Ищите здесь идею. Вы также должны проверить флаг перед любыми методами класса. Имеет смысл? Это сложно?
Джордао
1
+1 за «Еще лучшее решение - иметь правило, согласно которому вам всегда нужно создавать класс-оболочку для любого неуправляемого ресурса, с которым вам нужно работать» . Я наткнулся на это в дополнении для VLC, и я использую его с тех пор. Спасает так много головных болей ...
Франц Б.
37

Обратите внимание, что любая реализация IDisposable должна следовать шаблону ниже (IMHO). Я разработал этот шаблон на основе информации от нескольких превосходных «богов» .NET - Руководства по проектированию .NET Framework (обратите внимание, что MSDN по какой-то причине не следует этому!). Руководство по проектированию .NET Framework было написано Кшиштофом Квалиной (архитектором CLR в то время) и Брэдом Абрамсом (я считаю, что руководителем программы CLR в то время) и Биллом Вагнером ([Эффективный C #] и [Более эффективный C #] (просто возьмите ищите их на Amazon.com:

Обратите внимание, что вы НИКОГДА не должны реализовывать Finalizer, если ваш класс не содержит (не наследует) неуправляемых ресурсов. Как только вы реализуете Finalizer в классе, даже если он никогда не вызывается, он гарантированно будет жить для дополнительной коллекции. Он автоматически помещается в очередь финализации (которая выполняется в одном потоке). Кроме того, одно очень важное замечание ... весь код, выполняемый в Финализаторе (если вам необходимо его реализовать), ДОЛЖЕН быть поточно-ориентированным и исключительным! ПЛОХИЕ вещи произойдут иначе ... (то есть неопределенное поведение и в случае исключения фатальный неисправимый сбой приложения).

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

#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, похоже, нравится устанавливать флаг «disposed» в конце метода disposed, но мне это кажется неправильным. Избыточные вызовы «Распоряжаться» должны ничего не делать; в то время как обычно не следует ожидать, что Dispose будет вызываться рекурсивно, такие вещи могут произойти, если кто-то пытается избавиться от объекта, который был оставлен в недопустимом состоянии из-за исключения, возникшего во время конструирования или какой-либо другой операции. Я думаю, что использование Interlocked.Exchangeцелочисленного IsDisposedфлага в не виртуальной функции-оболочке было бы безопаснее.
суперкат
@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

Dave Black
источник
3
Я полагаю, что есть 5-й случай: если ваш класс подписывается на события, используя сильные ссылки, тогда вы должны реализовать IDisposable и отменить свою регистрацию в событиях метода Dispose.
Дидье А.
Привет, Дидибус. Да вы правы. Я забыл об этом. Я изменил свой ответ, чтобы включить это как случай. Спасибо.
Дэйв Блэк,
В документации MSDN для шаблона dispose добавлен еще один случай: «РАССМОТРИТЕ, реализующий базовый шаблон Dispose для классов, которые сами не содержат неуправляемые ресурсы или одноразовые объекты, но, вероятно, имеют подтипы, которые имеют. Отличным примером этого является System.IO. Класс .Stream. Хотя это абстрактный базовый класс, который не содержит никаких ресурсов, большинство его подклассов делают это, и поэтому он реализует этот шаблон. "
Гонен я
12

Использование лямбды вместо IDisposable.

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

  • знать, что они должны использовать IDisposable
  • не забудьте использовать «использование».

Мой новый предпочтительный метод состоит в том, чтобы использовать фабричный метод и лямбду

Представьте, что я хочу сделать что-то с SqlConnection (что-то, что должно быть заключено в использование). Классически вы бы сделали

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

Новый путь

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

В первом случае вызывающая сторона может просто не использовать синтаксис using. Во втором случае у пользователя нет выбора. Нет метода, который создает объект SqlConnection, вызывающая сторона должна вызывать DoWithConnection.

DoWithConnection выглядит так

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

MakeConnection сейчас приватный

PM100
источник
2
Обертывание вещей в лямбдах может быть хорошим подходом, но оно имеет пределы. Это не так уж плохо для ситуаций, когда фактически все потребители класса будут использовать блок «с помощью», но это будет запрещать ситуации, когда метод хранит IDisposable в поле класса (либо напрямую, либо в чем-то вроде итератора). ).
суперкат
@supercat, вы можете утверждать, что запрет хранения хранилищ ресурсов - это хорошо. Модель заимствования, которую я предлагаю здесь, заставляет вас быть осторожным при использовании ресурса
pm100
Это может быть хорошо, но может также сделать некоторые очень разумные операции очень трудными. Например, предположим, что тип читателя базы данных вместо реализации IEnumerable <T> предоставляет метод DoForAll(Action<T>) where T:IComparable<T>, вызывающий указанный делегат для каждой записи. Учитывая два таких объекта, каждый из которых будет возвращать данные в отсортированном порядке, как один из них выведет все элементы, которые существуют в одной коллекции, но не другой? Если реализованы типы IEnumerable<T>, можно выполнить операцию слияния, но это не сработает DoForAll.
Суперкат
Единственный способ объединить две DoForAllколлекции без необходимости сначала полностью копировать одну коллекцию в какую-то другую структуру - это использовать два потока, что было бы гораздо сложнее, чем просто использование пары IEnumerable и соблюдение осторожности. выпустить их.
Суперкат
-1: хороший ответ на вопрос, который не задавался. Это было бы отличным ответом на вопрос «как мне упростить потребление IDisposable объектов»
Джон Сондерс
10

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

Краткий ответ: нет

Длинный ответ:

Это позволит потребителю вашего класса использовать «использование». Вопрос, который я хотел бы задать, - зачем им это делать? Большинство разработчиков не будут использовать «использование», если они не знают, что они должны - и как они знают. Или

  • Это очевидно из опыта (класс сокетов, например)
  • его документировано
  • они осторожны и видят, что класс реализует IDisposable

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

Вы реализуете Idisposable, чтобы они могли использовать использование, но они не будут использовать использование, если вы не скажете им об этом.

Так что не делай этого

PM100
источник
1
Я не понимаю, почему разработчик не будет использовать using / dispose на объекте, реализующем IDisposable (если программа все равно не выйдет).
Адриан
1
Дело в том, что разработчик должен написать все вызовы для размещения во всех путях кода, которые приводят к его отсутствию ссылок. Так, например, если я помещаю экземпляр в словарь, когда я удаляю записи из словаря, я должен вызвать dispose. Это много хлопот, которые не нужны в этом случае - объект не должен быть утилизирован
pm100
3
@ pm100 Re: Самостоятельная реализация IDisposable - на codeproject.com/KB/dotnet/idisposable.aspx есть подробная статья, в которой обсуждаются редкие случаи, когда вы можете подумать об этом (я уверен, очень редко). Вкратце: если вы можете предвидеть потребность в IDisposable в будущем или в производном объекте, то вы можете подумать о реализации IDisposable в качестве «неоперативного» в своем базовом классе, чтобы избежать проблем с нарезкой, когда некоторые производные объекты требуют распоряжение, а другие нет.
Кевин П. Райс
4
  1. Если вы используете другие управляемые объекты, которые используют неуправляемые ресурсы, вы не несете ответственности за их завершение. Ваша обязанность - вызывать Dispose для этих объектов, когда Dispose вызывается для вашего объекта, и он на этом останавливается.

  2. Если ваш класс не использует ограниченные ресурсы, я не понимаю, почему вы заставили бы ваш класс реализовать IDisposable. Вы должны делать это только если вы:

    • Знайте, что скоро у вас будут ограниченные ресурсы в ваших объектах, просто не сейчас (и я имею в виду, что, как в «мы все еще разрабатываем, он будет здесь до того, как мы закончили»), а не в «Я думаю, что нам это понадобится «)
    • Использование ограниченных ресурсов
  3. Да, код, который использует ваш код, должен вызывать метод Dispose вашего объекта. И да, код, который использует ваш объект, может использовать, 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

Некоторые аспекты другого ответа немного некорректны по двум причинам:

Первый,

using(NoGateway objNoGateway = new NoGateway())

на самом деле эквивалентно:

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

Это может показаться нелепым, поскольку оператор 'new' никогда не должен возвращать 'null', если у вас нет исключения OutOfMemory. Но рассмотрим следующие случаи: 1. Вы вызываете FactoryClass, который возвращает ресурс IDisposable, или 2. Если у вас есть тип, который может или не может наследоваться от IDisposable, в зависимости от его реализации - помните, что я видел, что шаблон IDisposable реализован неправильно во многих время на многих клиентах, где разработчики просто добавляют метод Dispose (), не наследуя от IDisposable (плохо, плохо, плохо). Вы также можете иметь дело с возвращением ресурса IDisposable из свойства или метода (опять же, плохо, плохо, плохо - не отдавайте свои ресурсы IDisposable)

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

Если оператор «as» возвращает ноль (или свойство или метод, возвращающий ресурс), а ваш код в блоке «using» защищает от «ноль», ваш код не будет взорван при попытке вызвать Dispose для нулевого объекта из-за встроенная нулевая проверка.

Вторая причина, по которой ваш ответ не точный, заключается в следующем:

Финализатор вызывается при уничтожении вашего объекта GC

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

Я разместил правильную реализацию шаблона Dispose в моем блоге: Как правильно реализовать шаблон Dispose

Дэйв Блэк
источник
2
Вы уверены, что написали NoGateway = new NoGateway();и NoGateway != null?
Cœur
1
Это относится к stackoverflow.com/a/898856/3195477 ? Там нет ответа в настоящее время размещены под названием «Icey»
UuDdLrLrSs
@DaveInCaz, похоже, это правильно. Я нигде не вижу «Ледяной», но контекст моего ответа, похоже, направлен на ответ, приведенный по вашей ссылке выше. Может быть, он изменил свое имя пользователя?
Дейв Блэк
@DaveBlack круто, спасибо. Я только что отредактировал это прямо в тексте.
UuDdLrLrSs
2

1) WebClient является управляемым типом, поэтому вам не нужен финализатор. Финализатор необходим в том случае, если ваши пользователи не Dispose () вашего класса NoGateway и после этого необходимо очистить собственный тип (который не собирается GC). В этом случае, если пользователь не вызывает Dispose (), содержащийся WebClient будет удален GC сразу после того, как это сделает NoGateway.

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

Джесси С. Слайсер
источник
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);
         }
      }
   }
}
devnull
источник
1
using(NoGateway objNoGateway = new NoGateway())

эквивалентно

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Финализатор вызывается при уничтожении вашего объекта GC. Это может быть в совершенно другое время, чем когда вы покидаете свой метод. Dispose of IDisposable вызывается сразу после выхода из блока using. Следовательно, шаблон обычно используется для освобождения ресурсов сразу после того, как они вам больше не нужны.

Даниэль Фабиан
источник
1
Финализатор не вызывается при уничтожении объекта GC. Если «Завершить» переопределено, тогда, когда ГХ иначе уничтожил бы объект , он будет помещен в очередь объектов, нуждающихся в финализации, временно создавая сильную ссылку на него и - по крайней мере временно - «воскрешая» его.
суперкат
-5

Из того, что я знаю, настоятельно рекомендуется НЕ использовать Финализатор / Деструктор:

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

Главным образом, это происходит из-за незнания, когда или ЕСЛИ это будет вызвано. Метод утилизации намного лучше, особенно если вы используете или утилизируете напрямую.

использование это хорошо. используй это :)

Ник Мудрый
источник
2
Вы должны перейти по ссылке в ответе thecoop. Да, лучше использовать / Dispose, но класс Disposable определенно должен реализовывать оба.
Хенк Холтерман
Интересно, что все документы, которые я читал от Microsoft - например, рекомендации по разработке фреймворка - говорят, НИКОГДА не используют деструктор. Всегда используйте IDisposable.
Nic Wise
5
Просто различайте использование класса и написание класса, прочитайте их снова.
Хенк Холтерман
stackoverflow.com/questions/2605412/… может помочь
Алекс Nolasco