Завершение / Утилизация шаблона в C # - PullRequest
360 голосов
/ 22 мая 2009

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

Ответы [ 13 ]

398 голосов
/ 22 мая 2009

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

117 голосов
/ 29 января 2010

Официальный шаблон для реализации 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
  }

}
36 голосов
/ 14 января 2010

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

Обратите внимание, что вы НИКОГДА не должны реализовывать Финализатор, если ваш класс не содержит (не наследует) неуправляемых ресурсов. Как только вы реализуете 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 );
    }
}

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

22 голосов
/ 14 января 2010

Я согласен с 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 (Team Library Library Team) есть хороший пост в блоге об этом здесь http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Одно очень важное замечание: если вы работаете с WCF и очищаете ресурсы, ПОЧТИ ВСЕГДА следует избегать использования блока. Есть много постов в блоге и некоторые на MSDN о том, почему это плохая идея. Я также написал об этом здесь - Не используйте 'using ()' с прокси WCF

12 голосов
/ 20 июля 2011

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

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

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

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

Представьте, что я хочу сделать что-то с 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 теперь приватно

10 голосов
/ 14 января 2010

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

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

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

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

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

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

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

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

4 голосов
/ 09 марта 2011

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

Во-первых,

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 для null объект из-за встроенной проверки нуля.

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

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

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

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

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

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

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

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

3 голосов
/ 28 августа 2014

Утилизировать шаблон:

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();
        }
    }
}
2 голосов
/ 28 марта 2010

Шаблон из 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);
         }
      }
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...