415 votes

Utilisation de la méthode Finalize/Dispose en C#

C# 2008

Je travaille sur ce sujet depuis un moment maintenant, et je suis toujours confus quant à l'utilisation des méthodes finalize et dispose dans le code. Mes questions sont les suivantes :

  1. Je sais que nous n'avons besoin d'un finaliseur que lorsque nous disposons de ressources non gérées. Cependant, si des ressources gérées font des appels à des ressources non gérées, est-il nécessaire d'implémenter un finaliseur ?

  2. Cependant, si je développe une classe qui n'utilise pas de ressource non gérée - directement ou indirectement, dois-je implémenter la méthode de gestion des ressources ? IDisposable pour permettre aux clients de cette classe d'utiliser l'instruction "using" ?

    Serait-il possible d'implémenter IDisposable juste pour permettre aux clients de votre classe d'utiliser l'instruction using ?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. J'ai développé ce code simple ci-dessous pour démontrer l'utilisation de 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);
        }
    }

Question sur le code source :

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

    Ainsi, 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 est-elle appelée automatiquement lorsque l'exécution atteint la fin du bloc using, ou le client doit-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 le WebClient dans ma classe NoGateway classe. Parce que WebClient met en œuvre la IDisposable cela signifie-t-il que WebClient utilise indirectement des ressources non gérées ? Existe-t-il une règle absolue pour suivre cette règle ? Comment puis-je savoir qu'une classe utilise des ressources non gérées ?

2 votes

Ce modèle de conception compliqué est-il réellement nécessaire pour résoudre ce problème de réduction des ressources ?

454voto

thecoop Points 23695

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

Lors de l'implémentation d'une classe scellée qui n'utilise pas de ressources non gérées, vous implémentez simplement une méthode Dispose comme pour les implémentations normales d'interfaces :

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

Remarquez que je n'ai pas déclaré de finalisateur dans B Vous ne devriez implémenter un finaliseur que si vous avez des ressources non gérées à éliminer. Le CLR traite les objets finalisables différemment des objets non finalisables, même si SuppressFinalize s'appelle.

Ainsi, vous ne devriez pas déclarer un finaliseur à moins d'y être obligé, mais vous donnez aux héritiers de votre classe un hook pour appeler votre Dispose et mettre en œuvre un finalisateur eux-mêmes s'ils utilisent directement des ressources non géré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 les ressources non gérées ( SafeHandle et les amis ne comptent pas, car ils déclarent leurs propres finaliseurs), alors n'implémentez pas de finalisateur, car la GC traite différemment les classes finalisables, même si vous supprimez ultérieurement le finalisateur. Notez également que, même si B n'a pas de finalisateur, il appelle quand même SuppressFinalize pour traiter correctement les sous-classes qui implémentent un finaliseur.

Lorsqu'une classe implémente l'interface IDisposable, cela signifie qu'il existe quelque part des ressources non gérées dont il faut se débarrasser lorsque vous avez fini d'utiliser la classe. Les ressources réelles sont encapsulées dans les classes ; vous n'avez pas besoin de les supprimer explicitement. Il suffit d'appeler Dispose() ou en enveloppant la classe dans un using(...) {} s'assurera que toutes les ressources non gérées sont éliminées si nécessaire.

29 votes

Je suis d'accord avec thecoop. Notez que vous n'avez pas besoin d'un finaliseur si vous ne traitez que des ressources gérées (en fait, vous ne devriez PAS essayer d'accéder à des objets gérés à partir de votre finaliseur (autre que "this"), car il n'y a pas d'ordre garanti dans lequel la GC nettoiera les objets. En outre, si vous utilisez .Net 2.0 ou une version plus récente, vous pouvez (et devriez) utiliser SafeHandles pour envelopper les handles non gérés. Les Safehandles réduisent considérablement la nécessité d'écrire des finaliseurs pour vos classes gérées. blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

5 votes

Je pense qu'il est préférable d'inclure un appel à MessageBox.Show("Error, " + GetType().Name + " not disposed") dans le finaliseur, car les objets jetables doivent TOUJOURS être éliminés, et si vous ne le faites pas, il est préférable d'en être averti le plus tôt possible.

102 votes

@erikkallen c'est une blague ? :)

126voto

Jordão Points 29221

Le modèle officiel pour implémenter 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();
  }

}

Un site encore mieux La solution consiste à établir une règle selon laquelle vous toujours devez créer une classe d'enveloppe pour toute ressource non gérée que vous devez 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 rare .

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 : ils n'ont plus besoin de se préoccuper des ressources non gérées . Ils seront simple à mettre en œuvre et à comprendre :

public class ManagedDisposable : IDisposable {

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

}

0 votes

@Kyle : Merci ! Je l'aime aussi :-) Il y a une suite à cela aquí .

7 votes

Cependant, une chose que je veux noter est que cela n'empêche pas d'être appelé une deuxième fois.

6 votes

@HuseyinUslu : c'est juste le essence du motif. Vous pouvez certainement ajouter un disposed et vérifiez en conséquence.

41voto

Dave Black Points 1396

Notez que toute implémentation d'IDisposable devrait suivre le modèle ci-dessous (IMHO). J'ai développé ce modèle en me basant sur des informations provenant de plusieurs excellents "dieux" de .NET : les Directives de conception de .NET Framework (notez que MSDN ne suit pas cette démarche pour une raison quelconque !) Les directives de conception du cadre .NET ont été rédigées par Krzysztof Cwalina (architecte CLR à l'époque), Brad Abrams (je crois que le responsable du programme CLR à l'époque) et Bill Wagner ([Effective C#] et [More Effective C#] (recherchez-les sur Amazon.com) :

Notez que vous ne devez JAMAIS implémenter un Finalizer à moins que votre classe ne contienne directement (et non hérite) des ressources non gérées. Une fois que vous implémentez un Finalizer dans une classe, même s'il n'est jamais appelé, il est garanti de vivre pour une collection supplémentaire. Il est automatiquement placé sur la Finalization Queue (qui fonctionne sur un seul thread). Par ailleurs, 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 exception-safe ! Sinon, de MAUVAISES choses se produiront... (c'est-à-dire un comportement indéterminé et, dans le cas d'une exception, un plantage fatal et irrécupérable de l'application).

Le modèle que j'ai mis au point (et pour lequel j'ai é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 que vous n'avez pas besoin de lister explicitement l'héritage de 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 publié cette mise en œuvre sur mon blog à l'adresse suivante : Comment implémenter correctement le modèle Dispose ?

0 votes

Quelqu'un peut-il également ajouter un modèle pour une classe dérivée (dérivant de cette classe de base) ?

3 votes

@akjoshi - J'ai mis à jour le modèle ci-dessus pour inclure le code pour une classe dérivée jetable. Notez également que vous ne devez JAMAIS implémenter un Finalizer dans une classe dérivée...

3 votes

Microsoft semble aimer placer l'indicateur "disposed" à la fin de la méthode disposed, mais cela me semble erroné. Les appels redondants à "Dispose" sont censés ne rien faire ; même si l'on ne s'attend pas normalement à ce que Dispose soit appelé de manière récursive, cela peut arriver si l'on essaie de se débarrasser d'un objet qui a été laissé dans un état invalide par une exception survenue pendant la construction ou une autre opération. Je pense que l'utilisation d'un Interlocked.Exchange sur un nombre entier IsDisposed dans la fonction wrapper non virtuelle serait plus sûr.

23voto

Dave Black Points 1396

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

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

  1. Votre classe contient explicitement (c'est-à-dire pas par héritage) toute ressource gérée qui implémente IDisposable et qui doit être nettoyée 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 toutes 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 (oui, vous pouvez utiliser des pointeurs en C# géré, mais ils doivent être déclarés dans des blocs "unsafe", etc. Dans le cas de ressources non gérées, vous devez également vous assurer d'appeler System.Runtime.InteropServices.Marshal.ReleaseComObject() sur le BRF. Même si le RCW est, en théorie, une enveloppe gérée, il y a toujours un comptage de références qui se fait sous la couverture.

  4. Si votre classe s'abonne à des événements en utilisant des références fortes. Vous devez vous désinscrire/se détacher des événements. Il faut toujours s'assurer que ces derniers ne sont pas nuls avant d'essayer de les désenregistrer/détacher !

  5. Votre classe contient toute combinaison des éléments ci-dessus...

Une alternative recommandée pour éviter de travailler avec des objets COM et de devoir utiliser Marshal.ReleaseComObject() est d'utiliser la classe System.Runtime.InteropServices.SafeHandle.

La BCL (Base Class Library Team) a publié 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 que vous nettoyez des ressources, vous devriez presque TOUJOURS éviter le bloc "using". Il existe de nombreux articles de blog et certains sur MSDN qui expliquent pourquoi c'est une mauvaise idée. J'ai également posté à ce sujet ici - N'employez pas 'using()' avec un proxy de WCF

3 votes

Je crois qu'il existe un 5e cas : Si votre classe s'abonne à des événements en utilisant des références fortes, alors vous devez implémenter IDisposable et vous désinscrire des événements dans la méthode Dispose.

0 votes

Salut didibus. Oui, vous avez raison. J'avais oublié cette question. J'ai modifié ma réponse pour l'inclure comme cas. Merci.

0 votes

La documentation MSDN pour le motif dispose ajoute un autre cas : "CONSIDÉREZ l'implémentation du Basic Dispose Pattern sur des classes qui ne contiennent pas elles-mêmes de ressources non gérées ou d'objets jetables, mais qui sont susceptibles d'avoir des sous-types qui en contiennent. La classe System.IO.Stream en est un bon exemple. Bien qu'il s'agisse d'une classe de base abstraite qui ne détient aucune ressource, la plupart de ses sous-classes en détiennent et, de ce fait, elle implémente ce modèle."

12voto

pm100 Points 8303

Personne n'a répondu à la question de savoir si vous devez 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 me pose est : pourquoi le feraient-ils ? La plupart des développeurs n'utiliseront pas 'using' à moins qu'ils ne sachent qu'ils doivent le faire - et comment le savent-ils ? Soit

  • Il est évident qu'ils ont de l'expérience (une classe de prise par exemple).
  • son document
  • ils sont prudents et peuvent voir que la classe implémente IDisposable

Ainsi, en implémentant IDisposable, vous indiquez aux développeurs (du moins à certains d'entre eux) que cette classe englobe quelque chose qui doit être libéré. Ils utiliseront 'using' - mais il y a d'autres cas où 'using' 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 certainement. Mais ce n'est pas nécessaire

Vous implémentez Idisposable pour leur permettre d'utiliser using, mais ils n'utiliseront pas using à moins que vous ne le leur demandiez.

Alors ne le faites pas.

1 votes

Je ne comprends pas pourquoi un développeur ne voudrait pas utiliser/disposer sur un objet implémentant IDisposable (à moins que le programme ne soit sur le point de se terminer de toute façon).

1 votes

Le point est qu'un développeur devrait écrire tous les appels à disposer dans tous les chemins de code qui résultent de la non-référencement de celui-ci. Donc, par exemple, si je place une instance dans un dictionnaire, lorsque je supprime des entrées du dictionnaire, je dois appeler dispose. C'est beaucoup de tracas qui ne sont pas nécessaires dans ce cas - l'objet n'a pas besoin d'être éliminé.

3 votes

@pm100 Re : Needlessly implementing IDisposable -- Un article détaillé est disponible à l'adresse suivante codeproject.com/KB/dotnet/idisposable.aspx qui aborde certains cas rares où vous pourriez vouloir y penser (très rares, j'en suis sûr). En bref : Si vous pouvez prévoir le besoin d'IDisposable dans le futur, ou dans un objet dérivé, alors vous pourriez penser à implémenter IDisposable comme un "no-op" dans votre classe de base pour éviter les problèmes de "slicing" où certains objets dérivés nécessitent une élimination et d'autres non.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X