web-dev-qa-db-fra.com

Finaliser / Supprimer le motif en C #

C # 2008

J'y travaille depuis un moment et je suis encore confus sur certaines questions. Mes questions sont ci-dessous

  1. Je sais que vous n’avez besoin d’un finaliseur que si vous disposez de ressources non gérées. Toutefois, si vous utilisez des ressources gérées qui appellent des ressources non gérées, devrez-vous toujours mettre en œuvre un finaliseur?

  2. Toutefois, si vous développez une classe qui n'utilise pas de ressources non gérées, directement ou indirectement, pouvez-vous implémenter le IDisposable afin que les clients de votre classe puissent utiliser l'instruction 'using'?

    Serait-il acceptable d'implémenter IDisposable simplement pour que les clients de votre classe puissent utiliser l'instruction using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
  3. J'ai développé ce code simple ci-dessous pour illustrer le modèle Finalize/Disposer:

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

Question sur le code source:

  1. Ici, je n’ai pas ajouté le finaliseur et, normalement, le finaliseur sera appelé par le GC et le finaliseur appellera Dispose. Comme je n'ai pas le finaliseur, quand dois-je appeler la méthode Dispose? Est-ce le client de la classe qui doit l'appeler?

    Donc, ma classe dans l'exemple s'appelle NoGateway et le client pourrait utiliser et disposer de la classe comme ceci:

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

    La méthode Dispose sera-t-elle automatiquement appelée lorsque l'exécution atteindra la fin du bloc using ou le client devra-t-il appeler manuellement la méthode dispose? c'est à dire.

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
    
  2. J'utilise la classe webclient dans ma classe NoGateway. Parce que le client Web implémente l'interface IDisposable, cela signifie-t-il que le client Web utilise indirectement des ressources non gérées? Y a-t-il une règle absolue à suivre à ce sujet? Comment savoir qu'une classe utilise des ressources non gérées?

362
ant2009

Le modèle IDisposable recommandé est ici . Lorsque vous programmez une classe qui utilise IDisposable, vous devez généralement utiliser deux modèles:

Lorsque vous implémentez une classe scellée qui n'utilise pas de ressources non gérées, vous implémentez simplement une méthode Dispose comme avec les implémentations d'interface normales:

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

Lorsque vous implémentez une classe non scellée, procédez comme suit:

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

Notez que je n'ai pas déclaré de finaliseur dans B; vous ne devez implémenter un finaliseur que si vous disposez de ressources réelles non gérées. Le CLR traite les objets finalisables différemment des objets non finalisables, même si SuppressFinalize est appelé.

Donc, vous ne devriez pas déclarer de finalisateur sauf si vous devez le faire, mais vous donnez un crochet à vos héritiers pour appeler votre Dispose et implémenter un finaliseur eux-mêmes s'ils utilisent directement des ressources non managées:

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

Si vous n'utilisez pas directement des ressources non gérées (SafeHandle et que les amis ne comptent pas, car ils déclarent leurs propres finaliseurs), n'implémentez pas de finaliseur, car le GC traite différemment les classes finalisables, même si vous plus tard supprimer le finaliseur. Notez également que, même si B n'a pas de finaliseur, il appelle toujours SuppressFinalize pour traiter correctement les sous-classes implémentant un finaliseur.

Lorsqu'une classe implémente l'interface IDisposable, cela signifie qu'il existe quelque part des ressources non gérées dont vous devriez vous débarrasser lorsque vous aurez fini d'utiliser la classe. Les ressources réelles sont encapsulées dans les classes; vous n'avez pas besoin de les supprimer explicitement. Le simple fait d'appeler Dispose() ou de placer la classe dans un using(...) {} s'assurera que les ressources non gérées sont supprimées au besoin.

402
thecoop

Le schéma officiel pour mettre en œuvre IDisposable est difficile à comprendre. Je crois que celui-ci est meilleur :

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Une solution encore meilleure consiste à avoir une règle selon laquelle vous devez toujours créer une classe wrapper pour toute ressource non gérée dont vous avez besoin. gérer:

public class NativeDisposable : IDisposable {

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

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

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Avec SafeHandle et ses dérivés, ces classes doivent être très rares .

Le résultat pour les classes jetables qui ne traitent pas directement avec des ressources non gérées, même en présence d'héritage, est puissant: , elles n'ont plus besoin de se préoccuper des ressources non gérées . Ils seront simples à implémenter et à comprendre:

public class ManagedDisposable : IDisposable {

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

}
117
Jordão

Notez que toute implémentation IDisposable doit suivre le modèle ci-dessous (IMHO). J'ai développé ce modèle à partir d'informations provenant de plusieurs excellents "dieux" .NET les directives de conception .NET Framework (notez que MSDN ne les a pas suivies pour une raison quelconque!). Les directives pour la conception du .NET Framework ont ​​été rédigées par Krzysztof Cwalina (architecte du CLR à l’époque) et Brad Abrams (je crois le gestionnaire de programme du CLR à l’époque) et Bill Wagner ([Effective C #] et [More Effective C #] (prenez juste une recherchez-les sur Amazon.com:

Notez que vous ne devez JAMAIS implémenter un finaliseur à moins que votre classe ne contienne directement (n'hérite pas) des ressources non gérées. Une fois que vous avez implémenté un finaliseur dans une classe, même s'il n'est jamais appelé, il est garanti qu'il conservera une collection supplémentaire. Il est automatiquement placé dans la file d'attente de finalisation (qui s'exécute sur un seul thread). En outre, une remarque très importante ... tout le code exécuté dans un Finalizer (si vous devez en implémenter un) DOIT être thread-safe ET except-safe! MAUVAIS choses se produiront autrement ... (c'est-à-dire un comportement indéterminé et, dans le cas d'une exception, un blocage irrécupérable de l'application).

Le modèle que j'ai mis en place (et écrit un extrait de code) est le suivant:

#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

Voici le code pour implémenter IDisposable dans une classe dérivée. Notez qu'il n'est pas nécessaire de lister explicitement l'héritage d'IDisposable dans la définition de la classe dérivée.

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

J'ai posté cette implémentation sur mon blog à l'adresse suivante: Comment implémenter correctement le modèle de disposition

36
Dave Black

Je suis d'accord avec pm1 (et j'aurais dû le dire explicitement dans mon précédent post).

Vous ne devriez jamais implémenter IDisposable dans une classe sauf si vous en avez besoin. Pour être très précis, il y a environ 5 fois où vous auriez besoin/devriez implémenter IDisposable:

  1. Votre classe contient explicitement (c’est-à-dire pas par héritage) toutes les ressources gérées qui implémentent IDisposable et doivent être nettoyées une fois que votre classe n’est plus utilisée. Par exemple, si votre classe contient une instance d'un Stream, DbCommand, DataTable, etc.

  2. Votre classe contient explicitement les ressources gérées qui implémentent une méthode Close () - par exemple. IDataReader, IDbConnection, etc. Notez que certaines de ces classes implémentent IDisposable en ayant Dispose () ainsi qu'une méthode Close ().

  3. Votre classe contient explicitement une ressource non gérée - par exemple, un objet COM, des pointeurs (vous pouvez utiliser des pointeurs dans C # géré, mais ils doivent être déclarés dans des blocs "non sécurisés", etc. Dans le cas de ressources non gérées, vous devez également veiller à appeler System.Runtime.InteropServices.Marshal. ReleaseComObject () sur le RCW Même si le RCW est, en théorie, un wrapper managé, il y a toujours un comptage des références sous les couvertures.

  4. Si votre classe s'abonne à des événements utilisant des références fortes. Vous devez vous désinscrire/vous détacher des événements. Assurez-vous toujours qu'ils ne sont pas nuls avant d'essayer de les désenregistrer/détacher!.

  5. Votre classe contient n'importe quelle combinaison de ce qui précède ...

Une alternative recommandée pour travailler avec des objets COM et devoir utiliser Marshal.ReleaseComObject () consiste à utiliser la classe System.Runtime.InteropServices.SafeHandle.

La BCL (Base Class Library Team) publie un bon article de blog à ce sujet ici http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Une remarque très importante à faire est que si vous travaillez avec WCF et nettoyez des ressources, vous devriez PRESQUE TOUJOURS éviter le bloc 'using'. Il existe de nombreux articles de blog et certains sur MSDN expliquant pourquoi cette idée est mauvaise. J'ai également posté à ce sujet ici - ne pas utiliser 'using ()' avec un proxy WCF

22
Dave Black

Utiliser lambdas au lieu de IDisposable.

Je n'ai jamais été enthousiasmé par l'ensemble de l'idée/IDisposable. Le problème est que l'appelant doit:

  • savent qu'ils doivent utiliser IDisposable
  • rappelez-vous d'utiliser 'using'.

Ma nouvelle méthode préférée consiste à utiliser une méthode d'usine et un lambda

Imaginez que je veuille faire quelque chose avec un SqlConnection (quelque chose qui devrait être emballé dans une utilisation). Classiquement tu ferais

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

Nouvelle façon

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

Dans le premier cas, l'appelant ne pourrait tout simplement pas utiliser la syntaxe using. Dans le second cas, l'utilisateur n'a pas le choix. Aucune méthode ne crée d'objet SqlConnection, l'appelant doit appeler DoWithConnection.

DoWithConnection ressemble à ceci

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

MakeConnection est maintenant privé

12
pm100

personne n'a répondu à la question de savoir si vous devriez implémenter IDisposable même si vous n'en avez pas besoin.

Réponse courte: non

Longue réponse:

Cela permettrait à un consommateur de votre classe d'utiliser "using". La question que je voudrais poser est la suivante: pourquoi le feraient-ils? La plupart des développeurs n'utilisent pas "utiliser" à moins de savoir qu'ils doivent le faire - et comment savent-ils. Soit

  • ses évidentes l'expérience (une classe de socket par exemple)
  • son documenté
  • ils sont prudents et peuvent voir que la classe implémente IDisposable

Donc, en implémentant IDisposable, vous dites aux développeurs (au moins certains) que cette classe termine quelque chose qui doit être publié. Ils utiliseront 'using' - mais il existe d'autres cas où l'utilisation n'est pas possible (la portée de l'objet n'est pas locale); et ils devront commencer à s'inquiéter de la durée de vie des objets dans ces autres cas - je m'inquiéterais à coup sûr. Mais ce n'est pas nécessaire

Vous implémentez Idisposable pour leur permettre d'utiliser, mais ils ne l'utiliseront pas à moins d'indication contraire.

Alors ne le fais pas

10
pm100
  1. Si vous utilisez d'autres objets gérés utilisant des ressources non gérées, il ne vous appartient pas de vous assurer que ceux-ci sont finalisés. Votre responsabilité consiste à appeler Dispose sur ces objets lorsque Dispose est appelé sur votre objet et il s’arrête là.

  2. Si votre classe n'utilise pas de ressources limitées, je ne vois pas pourquoi vous obligeriez votre classe à implémenter IDisposable. Vous ne devriez le faire que si vous êtes:

    • Sachez que vous aurez bientôt peu de ressources dans vos objets, mais pas maintenant (et je veux dire par là que "nous sommes encore en développement, ce sera ici avant que nous ayons terminé", pas comme dans "je pense que nous aurons besoin de cela ")
    • Utiliser des ressources rares
  3. Oui, le code qui utilise votre code doit appeler la méthode Dispose de votre objet. Et oui, le code qui utilise votre objet peut utiliser using comme vous l'avez montré.

  4. (2 encore?) Il est probable que WebClient utilise des ressources non gérées ou d'autres ressources gérées implémentant IDisposable. La raison exacte, cependant, n'est pas importante. Ce qui est important, c’est qu’Il ​​mette en œuvre IDisposable, et il vous incombe donc d’agir sur cette connaissance en disposant de l’objet lorsque vous en avez terminé, même s’il s’avère que WebClient n’utilise aucune autre ressource.

Certains aspects de autre réponse sont légèrement incorrects pour 2 raisons:

Première,

using(NoGateway objNoGateway = new NoGateway())

est réellement équivalent à:

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

Cela peut sembler ridicule, car le "nouvel" opérateur ne doit jamais renvoyer "null" sauf si vous avez une exception OutOfMemory. Mais considérez les cas suivants: 1. Vous appelez un FactoryClass qui retourne une ressource IDisposable ou 2. Si vous avez un type qui peut hériter ou non de IDisposable en fonction de son implémentation - souvenez-vous que j'ai vu le modèle IDisposable implémenté de manière incorrecte plusieurs fois sur de nombreux clients où les développeurs ajoutent simplement une méthode Dispose () sans hériter de IDisposable (bad, bad, bad). Vous pouvez également avoir le cas d’une ressource identifiable renvoyée d’une propriété ou d’une méthode (encore une fois, mauvaise, mauvaise, non, ne donnez pas vos ressources identifiables)

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

Si l'opérateur "en tant que" renvoie null (ou la propriété ou la méthode renvoyant la ressource) et que votre code dans le bloc "using" protège contre "null", votre code n'explosera pas lorsque vous essayez d'appeler Dispose sur un objet null en raison de: le contrôle null 'intégré'.

La deuxième raison pour laquelle votre réponse est inexacte est la suivante:

Un finaliseur est appelé sur le GC détruisant votre objet

Premièrement, la finalisation (ainsi que la GC elle-même) n’est pas déterministe. Le CLR détermine le moment où il appellera un finaliseur. c'est-à-dire que le développeur/code n'a aucune idée. Si le modèle IDisposable est correctement implémenté (comme je l'ai signalé ci-dessus) et que GC.SuppressFinalize () a été appelé, le finaliseur NE sera PAS appelé. C'est l'une des principales raisons de mettre correctement en œuvre le modèle. Comme il n'y a qu'un seul thread Finalizer par processus géré, quel que soit le nombre de processeurs logiques, vous pouvez facilement dégrader les performances en sauvegardant ou même en suspendant le thread Finalizer en oubliant d'appeler GC.SuppressFinalize ().

J'ai posté une mise en œuvre correcte du modèle d'élimination sur mon blog: Comment implémenter correctement le modèle d'élimination

4
Dave Black

Éliminer le motif:

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

Exemple d'héritage:

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

est équivalent à

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Un finaliseur est appelé sur le GC détruisant votre objet. Cela peut être à un moment totalement différent de celui où vous quittez votre méthode. L'élimination d'IDisposable est appelée immédiatement après que vous ayez quitté le bloc using. Par conséquent, le modèle consiste généralement à utiliser pour libérer des ressources immédiatement après ne plus en avoir besoin.

2
Daniel Fabian

1) WebClient est un type géré, vous n'avez donc pas besoin d'un finaliseur. Le finaliseur est nécessaire au cas où vos utilisateurs ne disposeraient pas de Dispose () de votre classe NoGateway et que le type natif (qui n'est pas collecté par le GC) doit être nettoyé après. Dans ce cas, si l'utilisateur n'appelle pas Dispose (), le GC Web contenu sera éliminé par le GC immédiatement après le NoGateway.

2) Indirectement oui, mais vous ne devriez pas avoir à vous en préoccuper. Votre code est correct en tant que tel et vous ne pouvez pas empêcher vos utilisateurs d’oublier très facilement Dispose ().

2
Jesse C. Slicer

Motif de 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);
         }
      }
   }
}
2
devnull