1802 votes

Utilisation correcte de l'interface IDisposable

Je sais qu'en lisant la documentation Microsoft que l'utilisation "primaire" du IDisposable est de nettoyer les ressources non gérées.

Pour moi, "non géré" signifie des choses comme les connexions aux bases de données, les sockets, les poignées de fenêtre, etc. Mais, j'ai vu du code où le Dispose() est mise en œuvre pour libérer géré ce qui me semble redondant, puisque le ramasseur de déchets devrait s'en charger pour vous.

Par exemple :

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Ma question est la suivante : est-ce que cela permet au ramasseur d'ordures de libérer la mémoire utilisée par MyCollection plus vite que d'habitude ?

editar : Jusqu'à présent, les gens ont posté de bons exemples d'utilisation d'IDisposable pour nettoyer les ressources non gérées comme les connexions aux bases de données et les bitmaps. Mais supposons que _theList dans le code ci-dessus contenait un million de chaînes, et vous vouliez libérer cette mémoire maintenant plutôt que d'attendre le ramasseur de déchets. Le code ci-dessus y parviendrait-il ?

38 votes

J'aime la réponse acceptée parce qu'elle vous indique le "modèle" correct d'utilisation d'IDisposable, mais comme l'OP l'a dit dans son édition, elle ne répond pas à sa question. IDisposable n'appelle pas la GC, il ne fait que marquer un objet comme pouvant être détruit. Mais quel est le vrai moyen de libérer de la mémoire "tout de suite" au lieu d'attendre que la GC intervienne ? Je pense que cette question mérite une discussion plus approfondie.

47 votes

IDisposable ne marque rien. Le site Dispose fait ce qu'elle doit faire pour nettoyer les ressources utilisées par l'instance. Cela n'a rien à voir avec la GC.

0 votes

@John - Je pense que vous avez mal compris ma question. Supposons que ma classe implémente IDisposable et que dans ma classe, j'utilise une très grande ArrayList ou un autre objet géré que je marque comme nul lorsque Dispose(true) est appelé sur une instance de ma classe. Maintenant, selon le modèle, ce que je fais ici est de marquer la référence ArrayList comme nulle, mais les données des objets ArrayList seront toujours en mémoire jusqu'à ce que le GC entre en jeu, ce qui est non déterministe. La question est donc de savoir comment libérer ce bloc de mémoire non référencé immédiatement et de manière déterministe.

2795voto

Ian Boyd Points 50743

L'intérêt de Dispose est pour libérer les ressources non gérées. Il faut le faire à un moment donné, sinon elles ne seront jamais nettoyées. Le ramasseur d'ordures ne sait pas comment d'appeler DeleteHandle() sur une variable de type IntPtr Il ne sait pas. si ou non, il doit appeler DeleteHandle() .

Note : Qu'est-ce qu'un ressource non gérée ? Si vous l'avez trouvé dans le Microsoft .NET Framework : il est géré. Si vous êtes allé fouiller vous-même dans MSDN, c'est non géré. Tout ce que vous avez utilisé comme appels P/Invoke pour sortir du monde confortable de tout ce qui est disponible dans le .NET Framework n'est pas géré - et vous êtes maintenant responsable de son nettoyage.

L'objet que vous avez créé doit exposer algunos que le monde extérieur peut appeler, afin de nettoyer les ressources non gérées. La méthode peut être nommée comme vous le souhaitez :

public void Cleanup()

ou

public void Shutdown()

Mais au lieu de cela, il existe un nom standardisé pour cette méthode :

public void Dispose()

Une interface a même été créée, IDisposable qui n'a que cette seule méthode :

public interface IDisposable
{
   void Dispose()
}

Donc vous faites en sorte que votre objet expose le IDisposable et de cette façon vous promettez que vous avez écrit cette unique méthode pour nettoyer vos ressources non gérées :

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

Et vous avez terminé. Sauf que vous pouvez faire mieux.


Que faire si votre objet a alloué un 250MB System.Drawing.Bitmap (c'est-à-dire la classe Bitmap gérée par .NET) comme une sorte de tampon d'image ? Bien sûr, il s'agit d'un objet .NET géré, et le ramasseur d'ordures le libérera. Mais voulez-vous vraiment laisser 250 Mo de mémoire en suspens, en attendant que le ramasseur d'ordures éventuellement arrive et le libère ? Et s'il y a un ouvrir une connexion à la base de données ? Nous ne voulons certainement pas que cette connexion reste ouverte, en attendant que le GC finalise l'objet.

Si l'utilisateur a appelé Dispose() (ce qui signifie qu'ils n'ont plus l'intention d'utiliser l'objet) ; pourquoi ne pas se débarrasser de ces bitmaps et de ces connexions de base de données inutiles ?

Alors maintenant, nous le ferons :

  • se débarrasser des ressources non gérées (parce que nous le devons), et
  • se débarrasser des ressources gérées (parce que nous voulons être utiles)

Donc, mettons à jour notre Dispose() pour se débarrasser de ces objets gérés :

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

Et tout va bien, sauf que vous pouvez faire mieux !


Et si la personne a oublié d'appeler Dispose() sur votre objet ? Alors il y aurait des fuites non géré ressources !

Note : Ils ne fuient pas géré car le ramasseur d'ordures va finir par s'exécuter, sur un fil d'arrière-plan, et libérer la mémoire associée à tous les objets inutilisés. Cela comprendra votre objet, ainsi que tous les objets gérés que vous utilisez (par exemple, l'objet Bitmap et le DbConnection ).

Si la personne a oublié d'appeler Dispose() nous pouvons toujours sauver leur bacon ! Nous avons encore un moyen de l'appeler pour elles : lorsque le ramasseur d'ordures arrive enfin à libérer (c'est-à-dire à finaliser) notre objet.

Note : Le ramasseur d'ordures finira par libérer tous les objets gérés. Lorsqu'il le fait, il appelle la fonction Finalize sur l'objet. Le GC ne sait pas, ou ne ne se soucie pas de su Éliminer méthode. C'est juste un nom que nous avons choisi pour une méthode que nous appelons quand nous voulons nous se débarrasser de trucs non gérés.

La destruction de notre objet par le collecteur d'ordures est l'étape suivante. parfait Il est temps de libérer ces satanées ressources non gérées. Pour ce faire, nous surchargeons la fonction Finalize() méthode.

Note : En C#, vous ne surchargez pas explicitement la fonction Finalize() méthode. Vous écrivez une méthode qui ressemble à a Destructeur C++ et le le compilateur considère qu'il s'agit de votre implémentation de la fonction Finalize() méthode :

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Mais il y a un bug dans ce code. Vous voyez, le ramasseur de déchets fonctionne sur une base fil de fond ; vous ne connaissez pas l'ordre dans lequel deux objets sont détruits. Il est tout à fait possible que dans votre Dispose() le code géré L'objet dont vous essayez de vous débarrasser (parce que vous vouliez être utile) n'est plus là :

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Donc ce dont vous avez besoin est un moyen pour Finalize() de dire Dispose() qu'il devrait ne pas toucher à la gestion ressources (parce qu'elles pourrait ne pas être là plus), tout en libérant des ressources non gérées.

Le modèle standard pour faire cela est d'avoir Finalize() y Dispose() appellent tous deux un troisième ( !); où vous passez un booléen disant si vous l'appelez de Dispose() (par opposition à Finalize() ), ce qui signifie qu'il est possible de libérer les ressources gérées.

Ce site interne méthode pourrait peut recevoir un nom arbitraire comme "CoreDispose" ou "MyInternalDispose", mais la tradition veut qu'on l'appelle Dispose(Boolean) :

protected void Dispose(Boolean disposing)

Mais un nom de paramètre plus utile pourrait être :

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

Et vous changez votre implémentation de la IDisposable.Dispose() méthode à :

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

et votre finisseur à :

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Note : Si votre objet descend d'un objet qui implémente Dispose alors n'oubliez pas d'appeler leur base Dispose lorsque vous remplacez la méthode Dispose :

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

Et tout va bien, sauf que vous pouvez faire mieux !


Si l'utilisateur appelle Dispose() sur votre objet, alors tout a été nettoyé. Plus tard, lorsque le ramasseur d'ordures arrivera et appellera Finalize, il appellera alors Dispose encore.

Non seulement c'est un gaspillage, mais si votre objet contient des références inutiles à des objets dont vous vous êtes déjà débarrassé à partir de la fonction dernier Appel à Dispose() vous allez essayer de les jeter à nouveau !

Vous remarquerez que dans mon code, j'ai pris soin de supprimer les références aux objets que j'ai éliminés, de sorte que je n'essaie pas d'appeler Dispose sur une référence d'objet poubelle. Mais cela n'a pas empêché un bug subtil de se glisser.

Lorsque l'utilisateur appelle Dispose() : la poignée CursorFileBitmapIconServiceHandle est détruit. Plus tard, lorsque le ramasseur d'ordures s'exécutera, il tentera de détruire à nouveau le même identifiant.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

La façon de résoudre ce problème est de dire au ramasseur de déchets qu'il n'a pas besoin de finaliser l'objet - ses ressources ont déjà été nettoyées et il n'y a plus rien à faire. Vous faites cela en appelant GC.SuppressFinalize() dans le Dispose() méthode :

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Maintenant que l'utilisateur a appelé Dispose() nous avons :

  • ressources libérées non gérées
  • ressources gérées libérées

Il est inutile que le GC exécute le finisseur - tout est réglé.

Ne pourrais-je pas utiliser Finalize pour nettoyer les ressources non gérées ?

La documentation pour Object.Finalize dit :

La méthode Finalize est utilisée pour effectuer des opérations de nettoyage sur les ressources non gérées détenues par l'objet courant avant la destruction de l'objet.

Mais la documentation MSDN dit aussi, pour IDisposable.Dispose :

Exécute les tâches définies par l'application associées à la libération, au déblocage ou à la réinitialisation des ressources non gérées.

Alors, c'est lequel ? Quel est l'endroit où je dois nettoyer les ressources non gérées ? La réponse est :

C'est votre choix ! Mais choisissez Dispose .

Vous pouvez certainement placer votre nettoyage non géré dans le finaliseur :

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Le problème est que vous n'avez aucune idée du moment où le ramasseur de déchets finalisera votre objet. Vos ressources natives non gérées, non nécessaires, non utilisées resteront en place jusqu'à ce que le ramasseur d'ordures éventuellement fonctionne. Ensuite, il appellera votre méthode de finalisation pour nettoyer les ressources non gérées. La documentation de Object.Finalize souligne ce point :

Le moment exact où le finaliseur s'exécute n'est pas défini. Pour garantir la libération déterministe des ressources pour les instances de votre classe, implémentez une fonction de type Fermer ou fournir une IDisposable.Dispose mise en œuvre.

C'est la vertu de l'utilisation de Dispose pour nettoyer les ressources non gérées ; vous pouvez savoir, et contrôler, quand les ressources non gérées sont nettoyées. Leur destruction est "déterministe" .


Pour répondre à votre question initiale : Pourquoi ne pas libérer la mémoire maintenant, plutôt que pour le moment où la GC décide de le faire ? J'ai un logiciel de reconnaissance faciale qui besoins pour se débarrasser de 530 Mo d'images internes maintenant puisqu'ils ne sont plus nécessaires. Si nous ne le faisons pas, la machine s'arrête de tourner.

Lecture en prime

Pour tous ceux qui aiment le style de cette réponse (expliquer la por qué donc le comment devient évident), je vous suggère de lire le premier chapitre de l'ouvrage Essential COM de Don Box :

En 35 pages, il explique les problèmes liés à l'utilisation d'objets binaires et invente COM sous vos yeux. Une fois que vous aurez réalisé le por qué de COM, les 300 pages restantes sont évidentes, et ne font que détailler la mise en œuvre de Microsoft.

Je pense que tout programmeur qui a déjà eu affaire à des objets ou à des COM devrait, au minimum, lire le premier chapitre. C'est la meilleure explication de tout ce qui existe.

Lecture supplémentaire

Quand tout ce que vous savez est faux archives par Eric Lippert

Il est donc très difficile d'écrire un finisseur correct, et le meilleur conseil que je puisse te donner est de ne pas essayer. .

41 votes

C'est une excellente réponse mais je pense qu'elle bénéficierait d'une liste de code finale pour un cas standard et pour un cas où la classe dérive d'une classe de base qui implémente déjà Dispose, par exemple en ayant lu ici ( msdn.microsoft.com/fr/us/library/aa720161%28v=vs.71%29.aspx ) et je ne sais pas ce que je dois faire lorsque je dérive d'une classe qui implémente déjà Dispose (je suis nouveau dans ce domaine).

0 votes

Quel est l'effet de la mise à zéro des instances gérées lors de l'exécution de l'opération de gestion de la base de données ? Dispose() autre que de s'assurer qu'ils ne seront pas éliminés à nouveau parce que l'appel de la != null échouerait ? Qu'en est-il des types gérés qui ne sont pas Disposable ? Doivent-ils être traités dans le Dispose (par exemple, Set to null) ? Faut-il le faire pour tous les objets gérés, ou seulement pour ceux que l'on considère comme "lourds" et qui valent la peine de faire quelque chose avant que la GC n'intervienne ? Je suppose que cela ne concerne que les Disposable membres d'une classe, mais system.Drawing.Image mentionné comme exemple ne semble pas être jetable...

0 votes

@Bartosz Vous pouvez définir n'importe quelle variable que vous voulez pour null à l'intérieur de votre Dispose méthode. Donner à une variable la valeur null signifie qu'il ne fait que puede être collecté plus tôt (puisqu'il n'a pas de références en suspens). Si un objet n'implémente pas IDisposable alors vous n'avez pas à vous en débarrasser. Un objet n'expose que Dispose si elle besoins à éliminer.

76voto

yfeldblum Points 42613

IDisposable est souvent utilisé pour exploiter le using et profiter d'un moyen facile de faire un nettoyage déterministe des objets gérés.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

46voto

Scott Dorman Points 25000

L'objectif du motif Dispose est de fournir un mécanisme permettant de nettoyer les ressources gérées et non gérées. Le moment où cela se produit dépend de la façon dont la méthode Dispose est appelée. Dans votre exemple, l'utilisation de Dispose n'a rien à voir avec la disposition, puisque l'effacement d'une liste n'a aucun impact sur la collection en cours de disposition. De même, les appels visant à définir les variables comme nulles n'ont pas non plus d'impact sur le GC.

Vous pouvez jeter un coup d'oeil à ceci article pour plus de détails sur la façon d'implémenter le modèle Dispose, mais cela ressemble essentiellement à ceci :

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

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

La méthode la plus importante ici est la méthode Dispose(bool), qui s'exécute en fait dans deux circonstances différentes :

  • disposing == true : la méthode a été appelée directement ou indirectement par le code d'un utilisateur. Les ressources gérées et non gérées peuvent être disposées.
  • disposing == false : la méthode a été appelée par le runtime depuis l'intérieur du finaliseur, et vous ne devez pas référencer d'autres objets. Seules les ressources non gérées peuvent être disposées.

Le problème avec le fait de laisser le GC s'occuper du nettoyage est que vous n'avez aucun contrôle sur le moment où le GC va lancer un cycle de collecte (vous pouvez appeler GC.Collect(), mais vous ne devriez vraiment pas), de sorte que les ressources peuvent rester plus longtemps que nécessaire. Rappelez-vous que l'appel à Dispose() ne provoque pas réellement un cycle de collecte ou n'oblige pas le GC à collecter/libérer l'objet ; il fournit simplement le moyen de nettoyer de manière plus déterministe les ressources utilisées et indique au GC que ce nettoyage a déjà été effectué.

Le but d'IDisposable et du motif dispose n'est pas de libérer immédiatement de la mémoire. La seule fois où un appel à Dispose a une chance de libérer immédiatement la mémoire est lorsqu'il gère le scénario disposing == false et manipule des ressources non gérées. Pour le code géré, la mémoire ne sera pas réellement récupérée avant que le GC n'exécute un cycle de collecte, sur lequel vous n'avez vraiment aucun contrôle (à part appeler GC.Collect(), ce qui, comme je l'ai déjà mentionné, n'est pas une bonne idée).

Votre scénario n'est pas vraiment valable puisque les chaînes dans .NET n'utilisent pas de ressources non modifiées et n'implémentent pas IDisposable, il n'y a aucun moyen de les forcer à être "nettoyées".

22voto

Daniel Earwicker Points 63298

Il ne devrait pas y avoir d'autres appels aux méthodes d'un objet après que Dispose ait été appelé sur lui (bien qu'un objet devrait tolérer d'autres appels à Dispose). L'exemple de la question est donc stupide. Si Dispose est appelé, alors l'objet lui-même peut être éliminé. L'utilisateur doit donc simplement supprimer toutes les références à cet objet (en leur attribuant la valeur null) et tous les objets internes qui lui sont liés seront automatiquement nettoyés.

En ce qui concerne la question générale sur la gestion/non-gestion et la discussion dans d'autres réponses, je pense que toute réponse à cette question doit commencer par une définition d'une ressource non gérée.

En résumé, il existe une fonction que vous pouvez appeler pour mettre le système dans un état, et une autre fonction que vous pouvez appeler pour le faire sortir de cet état. Dans l'exemple typique, la première fonction peut être une fonction qui renvoie un identifiant de fichier, et la seconde peut être un appel à la fonction CloseHandle .

Mais - et c'est là l'essentiel - il pourrait s'agir de n'importe quelle paire de fonctions correspondantes. L'une construit un état, l'autre le détruit. Si l'état a été construit mais pas encore démoli, alors une instance de la ressource existe. Vous devez faire en sorte que le démantèlement se produise au bon moment - la ressource n'est pas gérée par le CLR. Le seul type de ressource géré automatiquement est la mémoire. Il en existe deux types : le GC et la pile. Les types de valeurs sont gérés par la pile (ou en se glissant dans les types de référence), et les types de référence sont gérés par la GC.

Ces fonctions peuvent provoquer des changements d'état qui peuvent être librement intercalés, ou qui doivent être parfaitement imbriqués. Les changements d'état peuvent être threadsafe, ou non.

Regardez l'exemple dans la question de la Justice. Les modifications de l'indentation du fichier Log doivent être parfaitement imbriquées, sinon tout va de travers. De plus, il est peu probable qu'elles soient threadsafe.

Il est possible de faire du stop avec le collecteur d'ordures pour que vos ressources non gérées soient nettoyées. Mais seulement si les fonctions de changement d'état sont threadsafe et si deux états peuvent avoir des durées de vie qui se chevauchent de quelque manière que ce soit. L'exemple de ressource de Justice ne doit donc PAS avoir de finaliseur ! Cela n'aiderait personne.

Pour ce type de ressources, vous pouvez simplement mettre en place IDisposable sans finalisateur. Le finisseur est absolument facultatif - il doit l'être. Ce point est passé sous silence ou n'est même pas mentionné dans de nombreux ouvrages.

Vous devez alors utiliser le using pour avoir une chance de garantir que Dispose est appelé. C'est essentiellement comme faire du stop avec la pile (de sorte que le finaliseur est au GC, using est à la pile).

La partie manquante est que vous devez écrire manuellement Dispose et faire en sorte qu'il appelle vos champs et votre classe de base. Les programmeurs C++/CLI n'ont pas à le faire. Le compilateur l'écrit pour eux dans la plupart des cas.

Il existe une alternative, que je préfère pour les états qui s'imbriquent parfaitement et qui ne sont pas threadsafe (en dehors de toute autre chose, éviter IDisposable vous épargne le problème d'avoir une dispute avec quelqu'un qui ne peut pas résister à ajouter un finalizer à chaque classe qui implémente IDisposable).

Au lieu d'écrire une classe, vous écrivez une fonction. La fonction accepte un délégué à rappeler :

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Et puis un exemple simple serait :

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Le lambda qui est passé sert de bloc de code, c'est comme si vous créiez votre propre structure de contrôle pour servir le même but que using sauf qu'il n'y a plus de risque que l'appelant en abuse. Il n'y a aucune chance qu'ils ne parviennent pas à nettoyer la ressource.

Cette technique est moins utile si la ressource est du genre à avoir des durées de vie qui se chevauchent, parce qu'alors vous voulez pouvoir construire la ressource A, puis la ressource B, puis tuer la ressource A et plus tard la ressource B. Vous ne pouvez pas le faire si vous avez forcé l'utilisateur à se nicher parfaitement comme ceci. Mais alors vous devez utiliser IDisposable (mais toujours sans finalisateur, à moins que vous n'ayez implémenté la threadsafety, ce qui n'est pas gratuit).

17voto

olli Points 1945

Scénarios d'utilisation d'IDisposable : nettoyage des ressources non gérées, désabonnement aux événements, fermeture des connexions.

L'idiome que j'utilise pour implémenter IDisposable ( non threadsafe ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

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

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

1 votes

C'est presque l'implémentation du modèle Dispose de Microsoft, sauf que vous avez oublié de rendre DIspose(bool) virtuel. Le modèle en lui-même n'est pas un très bon modèle et devrait être évité à moins que vous n'ayez absolument besoin de disposer d'une partie de la hiérarchie d'héritage.

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