Завершить / утилизировать шаблон в C#


C# 2008

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

  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. здесь я не добавил финализатор, и, как правило, финализатор будет вызван сборщик мусора, и финализатор будет вызывать метод 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 косвенно использует неуправляемые ресурсы? Есть жесткое правило по этому поводу? Как узнать, что класс использует неуправляемые ресурсы?

13 338

13 ответов:

рекомендуемый шаблон 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(...) {} будет убедиться, что любые неуправляемые ресурсы избавляются по мере необходимости.

официальный шаблон для реализации IDisposable - это трудно понять. Я считаю, что это лучше:

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

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

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
  }

}

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

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

#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 );
    }
}

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

Я согласен с pm100 (и должен был явно сказать это в моем предыдущем посте).

вы никогда не должны реализовывать IDisposable в классе, если вам это нужно. Чтобы быть очень конкретным, есть около 5 раз, когда вам когда-либо понадобится/следует реализовать IDisposable:

  1. ваш класс явно содержит (т. е. не через наследование) любые управляемые ресурсы, которые реализуют IDisposable и должны быть очищены после того, как ваш класс больше не используется. Например, если ваш класс содержит экземпляр потока, DbCommand, DataTable и т. д.

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

  3. ваш класс явно содержит неуправляемый ресурс-например, COM-объект, указатели (да, вы можете использовать указатели в управляемом C#, но они должны быть объявлены в "небезопасных" блоках и т. д. В случае неуправляемых ресурсов, вы также должны убедиться, чтобы назвать систему.Во время выполнения.InteropServices.Маршал.ReleaseComObject () на RCW. Несмотря на то, что RCW, теоретически, является управляемой оболочкой, под обложками все еще продолжается подсчет ссылок.

  4. Если ваш класс подписывается на события, используя сильные ссылки. Вам нужно отменить регистрацию/отделить себя от событий. Всегда, чтобы убедиться, что они не являются нулевыми в первую очередь прежде чем пытаться отменить/отключить их!.

  5. ваш класс содержит любую комбинацию из вышеперечисленного...

рекомендуемая альтернатива работе с COM-объектами и необходимости использования Маршала.ReleaseComObject () - это использование системы.Во время выполнения.InteropServices.Класса SafeHandle.

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

одно очень важное замечание состоит в том, что если вы работаете с WCF и очищаете ресурсы, вы почти всегда должны избегать блока "использование". Есть много сообщений в блоге, там и некоторые на MSDN о том, почему это плохая идея. Я также написал об этом здесь - не используйте 'using ()' с прокси WCF

используя лямбда-выражения вместо интерфейс IDisposable.

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

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

мой новый предпочтительный метод-использовать заводской метод и лямбда вместо

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

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 - это теперь частная

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

короткий ответ:

ответ:

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

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

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

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

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

  1. Если вы используете другие управляемые объекты, использующие неуправляемые ресурсы, вы не несете ответственности за их завершение. Ваша ответственность заключается в вызове Dispose для этих объектов, когда Dispose вызывается для вашего объекта, и он останавливается там.

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

    • знаю, что вы будете у вас есть ограниченные ресурсы в ваших объектах скоро, просто не сейчас (и я имею в виду, что как в "мы все еще развиваемся, это будет здесь, прежде чем мы закончим", а не как в "Я думаю, что нам это понадобится")
    • использование дефицитных ресурсов
  3. Да, код, который использует ваш код должен вызвать метод Dispose объекта. И да, код, который использует ваш объект может использовать using Как вы показали.

  4. (2 раз?) Вполне вероятно, что WebClient использует либо неуправляемые ресурсы, либо другие управляемые ресурсы, реализующие IDisposable. Точная причина, однако, не важна. Важно то, что он реализует IDisposable, и поэтому вам приходится действовать на основе этого знания, избавляясь от объекта, когда вы закончите с ним, даже если окажется, что WebClient не использует никаких других ресурсов вообще.

@Icey,

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

первый,

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 (bad, bad, bad). Вы также можете иметь дело с IDisposable ресурсом, возвращаемым из свойства или метода (опять плохо, плохо, плохо - не " отдавайте свои 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 поток финализатора, независимо от количества логических процессоров, вы можете легко снизить производительность, создав резервную копию или даже повесив поток финализатора, забыв вызвать СБОРЩИК МУСОРА.SuppressFinalize().

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

распоряжаться шаблон:

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();
        }
    }
}
using(NoGateway objNoGateway = new NoGateway())

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

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

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

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

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);
         }
      }
   }
}

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

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

в основном, это связано с тем, что вы не знаете, когда или если он будет вызван. Метод dispose намного лучше, особенно если вы используете или утилизируете напрямую.

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