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

С# 2008

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

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

  • Однако, если вы разрабатываете класс, который не использует неуправляемые ресурсы, прямо или косвенно, можете ли вы реализовать IDisposable, чтобы клиенты вашего класса могли использовать "using statement"?

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

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
  • Я разработал этот простой код ниже, чтобы продемонстрировать шаблон 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 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);
        }
    }
    

Вопрос об исходном коде:

  • Здесь я не добавил финализатор, и обычно финализатор будет вызываться GC, и финализатор вызовет Dispose. Поскольку у меня нет финализатора, когда я вызываю метод Dispose? Это клиент этого класса, который должен называть его?

    Итак, мой класс в примере называется NoGateway, и клиент может использовать и распоряжаться классом следующим образом:

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

    Будет ли автоматически вызываться метод Dispose, когда выполнение достигнет конца используемого блока, или клиент должен вручную вызвать метод dispose? то есть.

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

Ответ 1

Рекомендуемый шаблон 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 имеет дело с finalizable классами по-разному, даже если вы позже подавите финализатор. Также обратите внимание, что даже если B не имеет финализатора, он по-прежнему вызывает SuppressFinalize, чтобы правильно обрабатывать любые подклассы, которые реализуют финализатор.

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

Ответ 2

Официальную схему реализации IDisposable трудно понять. Я считаю, что этот better:

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
  }

}

Ответ 3

Обратите внимание, что любая реализация IDisposable должна соответствовать шаблону ниже (IMHO). Я разработал этот шаблон, основанный на информации от нескольких отличных "богов".NET. . Рекомендации по дизайну NET Framework (обратите внимание, что MSDN не следует за этим для некоторых причина!). Руководство по дизайну .NET Framework написано Кшиштофом Квалиной (архитектор CLR в то время) и Брэдом Абрамсом (я считаю, что в то время был менеджером программ CLR) и Биллом Вагнером ([Эффективный С#] и [Более эффективный С#] (просто возьмите ищите их на 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 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

Ответ 4

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

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

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

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

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

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

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

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

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

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

Ответ 5

Использование lambdas вместо IDisposable.

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

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

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

Представьте, что я хочу что-то сделать с помощью 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 теперь закрыт

Ответ 6

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

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

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

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

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

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

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

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

Ответ 7

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

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

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

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

Ответ 8

@Icey,

На самом деле ваш ответ незначительно неверен по двум причинам:

Во-первых,

using(NoGateway objNoGateway = new NoGateway())

фактически эквивалентен:

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

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

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

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

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

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

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

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

Ответ 9

Dispose pattern:

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

Ответ 10

using(NoGateway objNoGateway = new NoGateway())

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

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

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

Ответ 11

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

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

Ответ 12

Шаблон из 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);
         }
      }
   }
}

Ответ 13

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

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

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

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