Utilisation appropriée de l'interface IDisposable

1659

Je sais de la lecture de la documentation Microsoft que l'utilisation "principale" de l' IDisposableinterface est de nettoyer les ressources non gérées.

Pour moi, signifie « non gérés » des choses comme les connexions de base de données, prises, poignées de fenêtres, etc. Mais, j'ai le code vu où la Dispose()méthode est mis en œuvre pour libérer gérées les ressources, ce qui me semble redondant, puisque le collecteur des ordures devrait prendre soin de c'est 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: cela fait-il que la mémoire du collecteur d'ordures est utilisée MyCollectionplus rapidement qu'elle ne le ferait normalement?

edit : Jusqu'à présent, les gens ont publié de bons exemples d'utilisation d'IDisposable pour nettoyer les ressources non gérées telles que les connexions aux bases de données et les bitmaps. Mais supposons que _theListdans le code ci-dessus contienne un million de chaînes, et que vous vouliez libérer cette mémoire maintenant , plutôt que d'attendre le garbage collector. Le code ci-dessus accomplirait-il cela?

cwick
la source
34
J'aime la réponse acceptée car elle vous indique le «modèle» correct d'utilisation d'IDisposable, mais comme l'OP l'a dit dans sa modification, elle ne répond pas à la question voulue. IDisposable «n'appelle» pas le GC, il «marque» simplement un objet comme destructible. Mais quelle est la véritable façon de libérer de la mémoire "en ce moment" au lieu d'attendre que GC se déclenche? Je pense que cette question mérite plus de discussion.
Punit Vora
40
IDisposablene marque rien. La Disposeméthode fait ce qu'elle doit faire pour nettoyer les ressources utilisées par l'instance. Cela n'a rien à voir avec GC.
John Saunders
4
@John. Je comprends IDisposable. Et c'est pourquoi j'ai dit que la réponse acceptée ne répond pas à la question prévue par le PO (et à la modification de suivi) quant à savoir si IDisposable aidera à <i> libérer de la mémoire </i>. Comme cela IDisposablen'a rien à voir avec la libération de mémoire, seulement des ressources, alors comme vous l'avez dit, il n'est pas nécessaire de définir les références gérées sur null, ce que fait OP dans son exemple. Donc, la bonne réponse à sa question est "Non, cela n'aide pas à libérer de la mémoire plus rapidement. En fait, cela n'aide pas du tout à libérer de la mémoire, seulement des ressources". Mais de toute façon, merci pour votre contribution.
Punit Vora
9
@desigeek: si c'est le cas, alors vous n'auriez pas dû dire "IDisposable" n'appelle "pas le GC, il" marque "simplement un objet comme destructible"
John Saunders
5
@desigeek: Il n'y a aucun moyen garanti de libérer de la mémoire de manière déterministe. Vous pouvez appeler GC.Collect (), mais c'est une demande polie, pas une demande. Tous les threads en cours d'exécution doivent être suspendus pour que la récupération de place se poursuive - lisez le concept des points de sécurité .NET si vous voulez en savoir plus, par exemple msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Si un thread ne peut pas être suspendu, par exemple parce qu'il y a un appel dans du code non managé, GC.Collect () peut ne rien faire du tout.
Concrete Gannet

Réponses:

2609

Le but de Dispose est de libérer des ressources non managées. Cela doit être fait à un moment donné, sinon ils ne seront jamais nettoyés. Le garbage collector ne sait pas comment appeler DeleteHandle()à une variable de type IntPtr, il ne sait pas si oui ou non il doit appeler DeleteHandle().

Remarque : Qu'est-ce qu'une ressource non gérée ? Si vous l'avez trouvé dans Microsoft .NET Framework: c'est géré. Si vous avez vous-même fouillé MSDN, ce n'est pas géré. Tout ce que vous avez utilisé les appels P / Invoke pour sortir du monde agréable et confortable de tout ce qui est disponible dans le .NET Framework n'est pas géré - et vous êtes maintenant responsable de le nettoyer.

L'objet que vous avez créé doit exposer une méthode, 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 à la place, il existe un nom normalisé pour cette méthode:

public void Dispose()

Il y a même eu une interface créée IDisposable, qui n'a qu'une seule méthode:

public interface IDisposable
{
   void Dispose()
}

Donc, vous faites exposer votre objet à l' IDisposableinterface, et de cette façon vous promettez que vous avez écrit cette seule méthode pour nettoyer vos ressources non managées:

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

Et tu as fini. Sauf que vous pouvez faire mieux.


Que faire si votre objet a alloué un System.Drawing.Bitmap de 250 Mo (c'est-à-dire la classe Bitmap gérée .NET) comme une sorte de tampon de trame? Bien sûr, il s'agit d'un objet .NET géré et le garbage collector le libérera. Mais voulez-vous vraiment laisser 250 Mo de mémoire juste assis là - en attendant que le ramasse-miettes finisse par venir le libérer? Et s'il y a une connexion de base de données ouverte ? Certes, nous ne voulons pas que cette connexion reste ouverte, attendant que le GC finalise l'objet.

Si l'utilisateur a appelé Dispose()(ce qui signifie qu'il ne prévoit plus d'utiliser l'objet), pourquoi ne pas se débarrasser de ces bitmaps et connexions de base de données inutiles?

Alors maintenant, nous allons:

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

Mettons donc à jour notre Dispose()méthode pour nous 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()votre objet? Ensuite, ils fuiraient des ressources non gérées !

Remarque: Ils ne laisseront pas fuir les ressources gérées , car finalement le garbage collector va s'exécuter, sur un thread d'arrière-plan, et libérer la mémoire associée aux objets inutilisés. Cela inclura votre objet et tous les objets gérés que vous utilisez (par exemple le Bitmapet le DbConnection).

Si la personne a oublié d'appeler Dispose(), nous pouvons toujours conserver son bacon! Nous avons encore un moyen de l'appeler pour eux: quand le garbage collector se met enfin à libérer (c'est-à-dire finaliser) notre objet.

Remarque: Le garbage collector libérera éventuellement tous les objets gérés. Dans ce cas, il appelle la Finalize méthode sur l'objet. Le GC ne connaît pas ou ne se soucie pas de votre méthode Dispose . C'était juste un nom que nous avons choisi pour une méthode que nous appelons lorsque nous voulons nous débarrasser des trucs non gérés.

La destruction de notre objet par le garbage collector est le moment idéal pour libérer ces satanées ressources non gérées. Nous le faisons en remplaçant la Finalize()méthode.

Remarque: En C #, vous ne remplacez pas explicitement la Finalize()méthode. Vous écrivez une méthode qui ressemble à un destructeur C ++ , et le compilateur prend cela pour votre implémentation de la 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 garbage collector s'exécute sur un thread d'arrière - plan ; vous ne connaissez pas l'ordre dans lequel deux objets sont détruits. Il est tout à fait possible que dans votre Dispose()code, l' objet géré dont vous essayez de vous débarrasser (parce que vous vouliez être utile) ne soit 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 Finalize()de dire Dispose()qu'il ne devrait toucher aucune ressource gérée (car ils pourraient ne plus y être ), tout en libérant des ressources non gérées.

Le modèle standard pour ce faire est d'avoir Finalize()et les Dispose()deux appellent une troisième (!) Méthode; où vous passez un dicton booléen si vous l'appelez depuis Dispose()(par opposition à Finalize()), ce qui signifie qu'il est sûr de libérer des ressources gérées.

Cette méthode interne peut recevoir un nom arbitraire comme "CoreDispose" ou "MyInternalDispose", mais il est de tradition de l'appeler 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 en:

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

et votre finaliseur pour:

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

Remarque : Si votre objet descend d'un objet qui implémente Dispose, n'oubliez pas d'appeler leur méthode Dispose de base lorsque vous substituez 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()votre objet, alors tout a été nettoyé. Plus tard, lorsque le garbage collector arrive et appelle Finalize, il appelle à Disposenouveau.

Non seulement cela est un gaspillage, mais si votre objet a des références indésirables à des objets que vous avez déjà éliminés depuis le dernier appel Dispose(), vous essaierez de les éliminer à nouveau!

Vous remarquerez dans mon code que j'ai pris soin de supprimer les références aux objets que j'ai supprimés, donc je n'essaie pas d'appeler Disposeune référence d'objet indésirable. Mais cela n'a pas empêché un bug subtil de s'introduire.

Lorsque l'utilisateur appelle Dispose(): le handle CursorFileBitmapIconServiceHandle est détruit. Plus tard, lorsque le garbage collector s'exécute, il essaiera de détruire à nouveau la même poignée.

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 garbage collector qu'il n'a pas besoin de se soucier de finaliser l'objet - ses ressources ont déjà été nettoyées et aucun travail supplémentaire n'est nécessaire. Pour ce faire, appelez GC.SuppressFinalize()la 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:

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

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

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

La documentation de Object.Finalizedit:

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 actuel avant que l'objet ne soit détruit.

Mais la documentation MSDN indique également, pour IDisposable.Dispose:

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

Alors c'est quoi? Lequel est l'endroit pour moi de nettoyer les ressources non gérées? La réponse est:

C'est ton 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 garbage collector se mettra à finaliser votre objet. Vos ressources natives non gérées, non nécessaires et non utilisées resteront en place jusqu'à ce que le garbage collector s'exécute finalement . Ensuite, il appellera votre méthode de finalisation; nettoyer les ressources non gérées. La documentation d' Object.Finalize le souligne:

L'heure exacte à laquelle le finaliseur s'exécute n'est pas définie. Pour garantir une libération déterministe des ressources pour les instances de votre classe, implémentez une méthode Close ou fournissez une IDisposable.Disposeimplémentation.

C'est la vertu de l'utilisation Disposepour nettoyer les ressources non gérées; vous apprenez et contrôlez le nettoyage des ressources non gérées. Leur destruction est "déterministe" .


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

Lecture bonus

Pour tous ceux qui aiment le style de cette réponse (en expliquant le pourquoi , donc le comment devient évident), je vous suggère de lire le premier chapitre du COM essentiel de Don Box:

En 35 pages, il explique les problèmes d'utilisation des objets binaires et invente COM devant vos yeux. Une fois que vous comprenez le pourquoi de COM, les 300 pages restantes sont évidentes et détaillent simplement l'implémentation de Microsoft.

Je pense que tout programmeur qui a déjà traité d'objets ou de COM devrait, à tout le moins, lire le premier chapitre. C'est la meilleure explication de tout.

Lecture bonus supplémentaire

Quand tout ce que vous savez est faux par Eric Lippert

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

Ian Boyd
la source
12
Vous pouvez faire mieux - vous devez ajouter un appel à GC.SuppressFinalize () dans Dispose.
socle
55
@Daniel Earwicker: C'est vrai. Microsoft aimerait que vous cessiez complètement d'utiliser Win32 et que vous vous en teniez aux appels .NET Framework joliment abstraits, portables et indépendants des périphériques. Si vous voulez fouiller le système d'exploitation en dessous; parce que vous pensez savoir quel OS fonctionne: vous prenez votre vie en main. Toutes les applications .NET ne s'exécutent pas sur Windows ou sur un bureau.
Ian Boyd
34
C'est une excellente réponse, mais je pense qu'elle bénéficierait cependant 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, après avoir lu ici ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ), je me suis également trompé sur ce que je devrais faire lorsque je dérive de la classe qui implémente déjà Dispose ( hé je suis nouveau à cela).
integra753
5
@GregS et autres: en général, je ne prendrais pas la peine de définir des références null. Tout d'abord, cela signifie que vous ne pouvez pas les faire readonly, et deuxièmement, vous devez faire des !=nullvérifications très laides (comme dans l'exemple de code). Vous pourriez avoir un drapeau disposed, mais il est plus facile de ne pas s'en soucier. Le GC .NET est suffisamment agressif pour qu'une référence à un champ xne soit plus considérée comme «utilisée» au moment où elle passe la x.Dispose()ligne.
porges
7
Dans la deuxième page du livre de Don Box que vous avez mentionné, il utilise l'exemple d'une implémentation O (1) de l'algorithme de recherche, dont "les détails sont laissés comme exercice pour le lecteur". J'ai ri.
essuyez
65

IDisposableest souvent utilisé pour exploiter l' usinginstruction et profiter d'un moyen simple 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.");
        }
    }
}
yfeldblum
la source
6
J'aime ça personnellement, mais cela ne correspond pas vraiment aux directives de conception du cadre.
mqp
4
Je le considérerais comme une conception appropriée, car il permet des étendues déterministes faciles et des constructions / nettoyages d'étendues, en particulier lorsqu'il est mélangé avec des blocs de gestion des exceptions, de verrouillage et d'utilisation de ressources non managées de manière complexe. La langue offre cela comme une fonctionnalité de première classe.
yfeldblum
Il ne suit pas exactement les règles spécifiées dans le FDG mais c'est certainement une utilisation valide du modèle car il est nécessaire pour être utilisé par la "déclaration d'utilisation".
Scott Dorman
2
Tant que Log.Outdent ne lance pas, il n'y a certainement rien de mal à cela.
Daniel Earwicker
1
Les différentes réponses à Est-il abusif d'utiliser IDisposable et d '«utiliser» comme moyen d'obtenir un «comportement de portée» pour une sécurité d'exception? entrer dans un peu plus de détails sur les raisons pour lesquelles différentes personnes aiment / n'aiment pas cette technique. C'est quelque peu controversé.
Brian
44

Le modèle Dispose a pour objet de fournir un mécanisme de nettoyage des ressources gérées et non gérées et 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 ne fait en réalité rien de ce qui est lié à l'élimination, car l'effacement d'une liste n'a aucun impact sur la suppression de cette collection. De même, les appels pour définir les variables sur null n'ont également aucun impact sur le GC.

Vous pouvez consulter cet article pour plus de détails sur la façon d'implémenter le modèle Dispose, mais il 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 le 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 éliminées.
  • disposer == 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 éliminées.

Le simple fait de laisser le GC se charger du nettoyage est que vous n'avez aucun contrôle réel sur le moment où le GC exécutera un cycle de collecte (vous pouvez appeler GC.Collect (), mais vous ne devriez vraiment pas) pour que les ressources restent plus longtemps que nécessaire. N'oubliez pas que l'appel à Dispose () ne provoque pas réellement de cycle de collecte ni n'entraîne en aucune façon le GC à collecter / libérer l'objet; il fournit simplement les moyens de nettoyer de manière plus déterministe les ressources utilisées et indique au GC que ce nettoyage a déjà été effectué.

L'intérêt d'IDisposable et du modèle d'élimination ne consiste pas à libérer immédiatement la mémoire. La seule fois où un appel à Dispose aura même une chance de libérer immédiatement de la mémoire, c'est quand il gère le faux scénario d'élimination == et manipule des ressources non gérées. Pour le code managé, la mémoire ne sera pas réellement récupérée jusqu'à ce que le GC exécute un cycle de collecte, sur lequel vous n'avez vraiment aucun contrôle (autre que d'appeler GC.Collect (), que j'ai déjà mentionné n'est pas une bonne idée).

Votre scénario n'est pas vraiment valide, car les chaînes dans .NET n'utilisent aucune ressource non modifiée et n'implémentent pas IDisposable, il n'y a aucun moyen de les forcer à être «nettoyées».

Scott Dorman
la source
4
N'avez-vous pas oublié d'implémenter le finaliseur?
Budda
@Budda: Non, il utilise un SafeHandle. Pas besoin de destructeur.
Henk Holterman
9
+1 pour avoir ajouté le filet de sécurité pour plusieurs appels vers Dispose (). La spécification indique que plusieurs appels doivent être sécurisés. Trop de classes Microsoft ne parviennent pas à implémenter cela, et vous obtenez l'ennuyeux ObjectDisposedException.
Jesse Chisholm
5
Mais Dispose (bool disposing) est votre propre méthode sur votre classe SimpleCleanup et ne sera jamais appelée par le framework. Étant donné que vous l'appelez uniquement avec "true" en tant que paramètre, la «suppression» ne sera jamais fausse. Votre code est très similaire à l'exemple MSDN pour IDisposable, mais il manque le finaliseur, comme l'a souligné @Budda, d'où proviendrait l'appel avec disposing = false.
yoyo
19

Il ne doit plus y avoir d'appels aux méthodes d'un objet après l'appel de Dispose (bien qu'un objet doive tolérer d'autres appels à Dispose). Par conséquent, l'exemple de la question est stupide. Si Dispose est appelé, l'objet lui-même peut être rejeté. Ainsi, l'utilisateur doit simplement supprimer toutes les références à cet objet entier (les définir sur null) et tous les objets associés internes à celui-ci seront automatiquement nettoyés.

En ce qui concerne la question générale sur les ressources gérées / non gérées 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.

Cela revient à dire qu'il y a une fonction que vous pouvez appeler pour mettre le système dans un état, et il y a une autre fonction que vous pouvez appeler pour le faire sortir de cet état. Maintenant, dans l'exemple typique, la première peut être une fonction qui renvoie un descripteur de fichier et la seconde peut être un appel à CloseHandle.

Mais - et c'est la clé - il pourrait s'agir de n'importe quelle paire de fonctions correspondante. L'un construit un État, l'autre le démolit. Si l'état a été construit mais pas encore détruit, alors une instance de la ressource existe. Vous devez organiser le démontage 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 existe deux types: le GC et la pile. Les types de valeur sont gérés par la pile (ou en attelant un trajet à l'intérieur des types de référence), et les types de référence sont gérés par le GC.

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

Regardez l'exemple de la question de Justice. Les modifications apportées à l'indentation du fichier journal doivent être parfaitement imbriquées, sinon tout va mal. Il est également peu probable qu'ils soient threadsafe.

Il est possible de faire du stop avec le ramasse-miettes pour nettoyer vos ressources non gérées. Mais seulement si les fonctions de changement d'état sont threadsafe et que deux états peuvent avoir des durées de vie qui se chevauchent de quelque manière que ce soit. Ainsi, l'exemple d'une ressource par Justice ne doit PAS avoir de finaliseur! Cela n'aiderait personne.

Pour ces types de ressources, vous pouvez simplement implémenter IDisposable, sans finaliseur. Le finaliseur est absolument facultatif - il doit l'être. Ceci est passé sous silence ou même pas mentionné dans de nombreux livres.

Vous devez ensuite utiliser la usingdéclaration pour avoir la moindre chance de vous assurer qu'elle Disposeest appelée. C'est essentiellement comme atteler un tour avec la pile (de sorte que le finaliseur est au GC, usingest à la pile).

La partie manquante est que vous devez écrire manuellement Dispose et le faire appeler dans 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 ne sont pas threadsafe (en dehors de toute autre chose, éviter IDisposable vous évite d'avoir un argument avec quelqu'un qui ne peut pas résister à l'ajout d'un finaliseur à chaque classe qui implémente IDisposable) .

Au lieu d'écrire une classe, vous écrivez une fonction. La fonction accepte un délégué pour 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 transmis sert de bloc de code, c'est donc comme si vous créez votre propre structure de contrôle pour servir le même but que using, sauf que vous n'avez plus aucun danger que l'appelant en abuse. Il n'y a aucun moyen qu'ils ne parviennent pas à nettoyer la ressource.

Cette technique est moins utile si la ressource est du type qui peut avoir des durées de vie qui se chevauchent, car alors vous voulez pouvoir créer la ressource A, puis la ressource B, puis tuer la ressource A et ensuite tuer la ressource B. Vous ne pouvez pas le faire si vous avez forcé l'utilisateur à s'emboîter parfaitement comme ça. Mais alors vous devez utiliser IDisposable(mais toujours sans finaliseur, sauf si vous avez implémenté threadsafety, qui n'est pas gratuit).

Daniel Earwicker
la source
re: "Il ne devrait plus y avoir d'appels aux méthodes d'un objet après que Dispose y ait été appelé". "Devrait" étant le mot clé. Si vous avez des actions asynchrones en attente, elles peuvent arriver après que votre objet a été supprimé. Causer une exception ObjectDisposedException.
Jesse Chisholm
La vôtre semble être la seule réponse autre que la mienne qui touche à l'idée que les ressources non gérées encapsulent un état que le GC ne comprend pas. Un aspect clé d'une ressource non gérée, cependant, est qu'une ou plusieurs entités dont l'état pourrait avoir besoin de nettoyer son état peuvent continuer d'exister même si l'objet qui "possède" la ressource n'en a pas. Comment aimez-vous ma définition? Assez similaire, mais je pense que cela rend la "ressource" un peu plus substantielle (c'est "l'accord" de l'objet extérieur pour modifier son comportement, en échange d'une notification lorsque ses services ne sont plus nécessaires)
supercat
@supercat - si vous êtes intéressé, j'ai écrit le post suivant quelques jours après avoir écrit la réponse ci-dessus: smellegantcode.wordpress.com/2009/02/13/…
Daniel Earwicker
1
@DanielEarwicker: Article intéressant, bien que je puisse penser à au moins un type de ressource non gérée que vous ne couvrez pas vraiment: les abonnements à des événements à partir d'objets à longue durée de vie. Les abonnements aux événements sont fongibles, mais même si la mémoire était illimitée, leur élimination pourrait être coûteuse. Par exemple, un énumérateur pour une collection qui autorise la modification pendant l'énumération peut avoir besoin de s'abonner pour mettre à jour les notifications de la collection, et une collection peut être mise à jour plusieurs fois au cours de sa vie. Si les enquêteurs sont abandonnés sans se désinscrire ...
supercat
1
La paire d'opérations enteret exitest au cœur de la façon dont je pense d'une ressource. L'inscription / la désinscription à des événements doit s'intégrer sans difficulté. En termes de caractéristiques orthogonales / fongibles, il est pratiquement impossible de les distinguer d'une fuite de mémoire. (Ce n'est pas surprenant car l'abonnement ajoute simplement des objets à une liste.)
Daniel Earwicker
17

Scénarios que j'utilise IDisposable: nettoyer les ressources non managées, se désinscrire des événements, fermer les connexions

L'idiome que j'utilise pour implémenter IDisposable ( pas 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
}
olli-MSFT
la source
Une explication complète du modèle peut être trouvée sur msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ
3
ne devrait jamais avoir un finaliseur inclus, sauf si vous avez des ressources non gérées. Même dans ce cas, l'implémentation préférée consiste à encapsuler la ressource non gérée dans SafeHandle.
Dave Black
11

Oui, ce code est complètement redondant et inutile et il ne fait pas faire au garbage collector tout ce qu'il ne ferait pas autrement (une fois qu'une instance de MyCollection est hors de portée, c'est-à-dire.) Surtout les .Clear()appels.

Réponse à votre modification: en quelque sorte. Si je fais ça:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Il est fonctionnellement identique à celui-ci à des fins de gestion de la mémoire:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Si vous avez vraiment vraiment vraiment besoin de libérer la mémoire à l'instant même, appelez GC.Collect(). Il n'y a cependant aucune raison de le faire ici. La mémoire sera libérée en cas de besoin.

mqp
la source
2
re: "La mémoire sera libérée en cas de besoin." Dites plutôt "quand le GC décide que c'est nécessaire". Vous pouvez constater des problèmes de performances du système avant que le GC ne décide que la mémoire est vraiment nécessaire. Le libérer maintenant n'est peut-être pas essentiel, mais peut être utile.
Jesse Chisholm
1
Il existe certains cas d'angle dans lesquels l'annulation des références dans une collection peut accélérer la collecte des ordures des éléments auxquels il est fait référence. Par exemple, si un grand tableau est créé et rempli de références à de plus petits éléments nouvellement créés, mais qu'il n'est pas nécessaire très longtemps après cela, l'abandon du tableau peut entraîner la conservation de ces éléments jusqu'au prochain GC de niveau 2, tout en le mettant à zéro en premier, les articles peuvent être éligibles pour le prochain GC de niveau 0 ou de niveau 1. Bien sûr, avoir de gros objets éphémères sur le grand tas d'objets est dégueu de toute façon (je n'aime pas le design) mais ...
supercat
1
... mettre à zéro ces tableaux avant de les abandonner peut parfois réduire l'impact du GC.
supercat
11

Si des MyCollectiondéchets doivent être récupérés de toute façon, vous ne devriez pas avoir à les jeter. Cela ne fera que déstabiliser le CPU plus que nécessaire, et peut même invalider une analyse pré-calculée que le garbage collector a déjà effectuée.

J'utilise IDisposablepour faire des choses comme s'assurer que les threads sont éliminés correctement, ainsi que les ressources non gérées.

EDIT En réponse au commentaire de Scott:

La seule fois où les mesures de performances du GC sont affectées, c'est quand un appel au [sic] GC.Collect () est effectué "

Sur le plan conceptuel, le GC conserve une vue du graphique de référence de l'objet et de toutes les références à celui-ci à partir des cadres de pile des threads. Ce tas peut être assez volumineux et s'étendre sur plusieurs pages de mémoire. À titre d'optimisation, le GC met en cache son analyse des pages qui ne sont pas susceptibles de changer très souvent pour éviter une nouvelle numérisation inutile de la page. Le GC reçoit une notification du noyau lorsque les données d'une page changent, il sait donc que la page est sale et nécessite une nouvelle analyse. Si la collection est dans Gen0, il est probable que d'autres éléments de la page changent également, mais cela est moins probable dans Gen1 et Gen2. Pour l'anecdote, ces crochets n'étaient pas disponibles dans Mac OS X pour l'équipe qui a porté le GC sur Mac afin de faire fonctionner le plug-in Silverlight sur cette plate-forme.

Autre point contre l'élimination inutile des ressources: imaginez une situation où un processus se décharge. Imaginez également que le processus fonctionne depuis un certain temps. Il est probable que la plupart des pages de mémoire de ce processus ont été échangées sur le disque. À tout le moins, ils ne sont plus dans le cache L1 ou L2. Dans une telle situation, il n'y a aucun intérêt pour une application en cours de déchargement à échanger toutes ces données et pages de codes en mémoire pour `` libérer '' les ressources qui seront de toute façon libérées par le système d'exploitation à la fin du processus. Cela s'applique aux ressources gérées et même à certaines ressources non gérées. Seules les ressources qui maintiennent des threads non-fond actifs doivent être supprimées, sinon le processus restera actif.

Maintenant, pendant l'exécution normale, il existe des ressources éphémères qui doivent être nettoyées correctement (comme @fezmonkey souligne les connexions à la base de données, les sockets, les poignées de fenêtre ) pour éviter les fuites de mémoire non gérées. Voilà le genre de choses qui doivent être éliminées. Si vous créez une classe qui possède un thread (et par propriétaire, je veux dire qu'il l'a créé et qu'il est donc responsable de s'assurer qu'il s'arrête, au moins par mon style de codage), alors cette classe doit très probablement implémenter IDisposableet supprimer le thread pendant Dispose.

Le framework .NET utilise l' IDisposableinterface comme un signal, voire un avertissement, aux développeurs que la classe this doit être supprimée. Je ne peux penser à aucun type dans le cadre qui implémente IDisposable(à l'exclusion des implémentations d'interface explicite) où l'élimination est facultative.

Drew Noakes
la source
Calling Dispose est parfaitement valide, légal et encouragé. Les objets qui implémentent IDisposable le font généralement pour une raison. Le seul moment où les mesures de performances du GC sont affectées est lorsqu'un appel est effectué avec GC.Collect ().
Scott Dorman
Pour de nombreuses classes .net, l'élimination est "quelque peu" facultative, ce qui signifie que l'abandon des instances "généralement" ne causera aucun problème tant que l'on ne deviendra pas fou en créant de nouvelles instances et en les abandonnant. Par exemple, le code généré par le compilateur pour les contrôles semble créer des polices lorsque les contrôles sont instanciés et les abandonner lorsque les formulaires sont supprimés; si l'on crée et supprime des milliers de contrôles, cela pourrait bloquer des milliers de poignées GDI, mais dans la plupart des cas, les contrôles ne sont pas créés et détruits autant. Néanmoins, il faut toujours essayer d'éviter un tel abandon.
supercat
1
Dans le cas des polices, je soupçonne que le problème est que Microsoft n'a jamais vraiment défini quelle entité est responsable de la suppression de l'objet "police" affecté à un contrôle; dans certains cas, un contrôle peut partager une police avec un objet dont la durée de vie est plus longue, donc avoir le contrôle Supprimer la police serait mauvais. Dans d'autres cas, une police sera affectée à un contrôle et nulle part ailleurs, donc si le contrôle ne le supprime pas, personne ne le fera. Soit dit en passant, cette difficulté avec les polices aurait pu être évitée s'il y avait eu une classe FontTemplate distincte non jetable, car les contrôles ne semblent pas utiliser le handle GDI de leur police.
supercat
Sur le sujet des Dispose()appels facultatifs , voir: stackoverflow.com/questions/913228/…
RJ Cuthbertson
7

Dans l'exemple que vous avez publié, il ne "libère pas la mémoire maintenant". Toute la mémoire est récupérée, mais elle peut permettre à la mémoire d'être collectée dans une génération antérieure . Il faudrait exécuter des tests pour en être sûr.


Les directives de conception du cadre sont des lignes directrices et non des règles. Ils vous indiquent à quoi sert principalement l'interface, quand l'utiliser, comment l'utiliser et quand ne pas l'utiliser.

J'ai lu une fois du code qui était un simple RollBack () en cas d'échec en utilisant IDisposable. La classe MiniTx ci-dessous vérifierait un indicateur sur Dispose () et si l' Commitappel ne se produisait jamais, il s'appellerait alors Rollbacksur lui-même. Il a ajouté une couche d'indirection rendant le code appelant beaucoup plus facile à comprendre et à maintenir. Le résultat ressemblait à quelque chose comme:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

J'ai également vu le code de synchronisation / journalisation faire la même chose. Dans ce cas, la méthode Dispose () a arrêté le minuteur et enregistré que le bloc était sorti.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Voici donc quelques exemples concrets qui n'effectuent aucun nettoyage de ressources non gérées, mais utilisent avec succès IDisposable pour créer un code plus propre.

Robert Paulson
la source
Jetez un œil à l'exemple de @Daniel Earwicker utilisant des fonctions d'ordre supérieur. Pour l'analyse comparative, le calendrier, la consignation, etc. Cela semble beaucoup plus simple.
Aluan Haddad du
6

Je ne répéterai pas les choses habituelles sur l'utilisation ou la libération de ressources non gérées, qui ont toutes été couvertes. Mais je voudrais souligner ce qui semble être une idée fausse commune.
Étant donné le code suivant

Classe publique LargeStuff
  Implémente IDisposable
  _Large privé en tant que chaîne ()

  'Un code étrange qui signifie que _Large contient maintenant plusieurs millions de longues chaînes.

  Public Sub Dispose () Implémente IDisposable.Dispose
    _Large = Rien
  End Sub

Je me rends compte que la mise en œuvre jetable ne suit pas les directives actuelles, mais j'espère que vous avez tous compris l'idée.
Maintenant, lorsque Dispose est appelé, combien de mémoire est libérée?

Réponse: aucune.
Calling Dispose peut libérer des ressources non managées, il NE PEUT PAS récupérer la mémoire gérée, seul le GC peut le faire. Cela ne veut pas dire que ce qui précède n'est pas une bonne idée, suivre le modèle ci-dessus est toujours une bonne idée en fait. Une fois Dispose exécuté, rien n'empêche le GC de revendiquer à nouveau la mémoire utilisée par _Large, même si l'instance de LargeStuff peut toujours être à portée. Les chaînes de _Large peuvent également être de génération 0, mais l'instance de LargeStuff peut être de génération 2, donc encore une fois, la mémoire sera revendiquée plus tôt.
Il est inutile d'ajouter un finaliseur pour appeler la méthode Dispose illustrée ci-dessus. Cela retardera simplement le renouvellement de la mémoire pour permettre au finaliseur de fonctionner.

pipTheGeek
la source
1
Si une instance de LargeStuffexiste depuis assez longtemps pour atteindre la génération 2 et si elle _Largecontient une référence à une chaîne nouvellement créée qui se trouve dans la génération 0, alors si l'instance de LargeStuffest abandonnée sans annulation _Large, la chaîne référencée par _Largesera conservé jusqu'à la prochaine collection Gen2. La remise à zéro _Largepeut laisser la chaîne être éliminée lors de la prochaine collection Gen0. Dans la plupart des cas, l'annulation des références n'est pas utile, mais il peut y avoir des avantages dans certains cas.
supercat
5

Outre son utilisation principale comme moyen de contrôler la durée de vie des ressources système (entièrement couvert par la réponse impressionnante de Ian , bravo!), Le combo IDisposable / using peut également être utilisé pour définir le changement d'état des ressources globales (critiques) : la console , les threads , le processus , tout objet global comme une instance d'application .

J'ai écrit un article sur ce modèle: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Il illustre comment vous pouvez protéger certains états globaux souvent utilisés de manière réutilisable et lisible : couleurs de la console , culture actuelle des threads , propriétés des objets d'application Excel ...

Pragmateek
la source
4

Si quoi que ce soit, je m'attendrais à ce que le code soit moins efficace qu'en le laissant de côté.

L'appel des méthodes Clear () n'est pas nécessaire, et le GC ne le ferait probablement pas si le Dispose ne le faisait pas ...

Arjan Einbu
la source
2

Il y a des choses que l' Dispose()opération fait dans l'exemple de code qui pourraient avoir un effet qui ne se produirait pas en raison d'un GC normal de l' MyCollectionobjet.

Si les objets référencés par _theListou _theDictsont référencés par d'autres objets, alors cet objet List<>ou celui - Dictionary<>ci ne sera pas soumis à collection mais n'aura soudainement aucun contenu. S'il n'y avait pas d'opération Dispose () comme dans l'exemple, ces collections contiendraient toujours leur contenu.

Bien sûr, si telle était la situation, je l'appellerais une conception cassée - je souligne simplement (d'un point de vue pédant, je suppose) que l' Dispose()opération pourrait ne pas être complètement redondante, selon qu'il existe d'autres utilisations du List<>ou Dictionary<>qui ne le sont pas. montré dans le fragment.

Michael Burr
la source
Ce sont des domaines privés, donc je pense qu'il est juste de supposer que le PO ne leur donne pas de références.
mqp
1) le fragment de code n'est qu'un exemple de code, je souligne donc qu'il peut y avoir un effet secondaire facile à ignorer; 2) les champs privés sont souvent la cible d'une propriété / méthode getter - peut-être trop (les getter / setters sont considérés par certaines personnes comme un peu anti-modèle).
Michael Burr,
2

Un problème avec la plupart des discussions sur les «ressources non managées» est qu'elles ne définissent pas vraiment le terme, mais semblent impliquer qu'il a quelque chose à voir avec le code non managé. S'il est vrai que de nombreux types de ressources non managées interfacent avec du code non managé, il n'est pas utile de penser aux ressources non managées en de tels termes.

Au lieu de cela, il faut reconnaître ce que toutes les ressources gérées ont en commun: elles impliquent toutes un objet demandant à une `` chose '' extérieure de faire quelque chose en son nom, au détriment d'autres `` choses '', et l'autre entité acceptant de le faire jusqu'à ce que nouvel avis. Si l'objet devait être abandonné et disparaître sans laisser de trace, rien ne dirait jamais à cette «chose» extérieure qu'elle n'avait plus besoin de modifier son comportement au nom de l'objet qui n'existait plus; par conséquent, l'utilité de la chose serait définitivement diminuée.

Une ressource non gérée, alors, représente un accord par une `` chose '' extérieure pour modifier son comportement au nom d'un objet, ce qui nuirait inutilement à l'utilité de cette `` chose '' extérieure si l'objet était abandonné et cessait d'exister. Une ressource gérée est un objet qui bénéficie d'un tel accord, mais qui s'est inscrit pour recevoir une notification en cas d'abandon et qui utilisera cette notification pour mettre de l'ordre dans ses affaires avant sa destruction.

supercat
la source
Eh bien, OMI, la définition d'un objet non géré est claire; tout objet non GC .
Eonil
1
@Eonil: Unmanaged Object! = Ressource non gérée. Des choses comme les événements peuvent être implémentées entièrement à l'aide d'objets gérés, mais constituent toujours des ressources non gérées car - au moins dans le cas d'objets de courte durée souscrivant à des événements d'objets de longue durée de vie - le GC ne sait rien sur la façon de les nettoyer. .
supercat
2

IDisposable est bon pour se désinscrire d'événements.

Adam Speight
la source
2

Premier de définition. Pour moi, une ressource non gérée signifie une classe, qui implémente une interface IDisposable ou quelque chose créé avec l'utilisation d'appels à la DLL. GC ne sait pas comment gérer de tels objets. Si la classe n'a par exemple que des types de valeur, alors je ne considère pas cette classe comme une classe avec des ressources non managées. Pour mon code, je suis les pratiques suivantes:

  1. Si la classe créée par moi utilise des ressources non gérées, cela signifie que je devrais également implémenter une interface IDisposable afin de nettoyer la mémoire.
  2. Nettoyez les objets dès que j'ai fini de l'utiliser.
  3. Dans ma méthode Dispose, j'itère tous les membres IDisposable de la classe et j'appelle Dispose.
  4. Dans ma méthode Dispose, appelez GC.SuppressFinalize (this) afin d'avertir le garbage collector que mon objet a déjà été nettoyé. Je le fais car appeler GC est une opération coûteuse.
  5. Comme précaution supplémentaire, j'essaie de rendre possible l'appel de Dispose () plusieurs fois.
  6. Parfois, j'ajoute un membre privé _disposed et j'archive les appels de méthode si l'objet a été nettoyé. Et s'il a été nettoyé, générer le modèle ObjectDisposedException
    suivant montre ce que j'ai décrit en mots comme exemple de code:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }
Yuriy Zaletskyy
la source
1
"Pour moi, une ressource non gérée signifie une classe, qui implémente une interface IDisposable ou quelque chose créé avec l'utilisation d'appels à la DLL." Vous dites donc que tout type qui is IDisposabledevrait lui-même être considéré comme une ressource non gérée? Cela ne semble pas correct. De plus, si le type d'implémentation est un type de valeur pure, vous semblez suggérer qu'il n'a pas besoin d'être supprimé. Cela semble également faux.
Aluan Haddad du
Tout le monde juge par lui-même. Je n'aime pas ajouter quelque chose au code mien juste pour l'ajouter. Cela signifie que si j'ajoute IDisposable, cela signifie que j'ai créé une sorte de fonctionnalité que GC ne peut pas gérer ou je suppose qu'il ne sera pas en mesure de gérer correctement sa durée de vie.
Yuriy Zaletskyy
2

Votre exemple de code donné n'est pas un bon exemple d' IDisposableutilisation. Normalement, l' effacement du dictionnaire ne doit pas aller à la Disposeméthode. Les éléments du dictionnaire seront effacés et supprimés lorsqu'ils seront hors de portée. IDisposablel'implémentation est requise pour libérer de la mémoire / des gestionnaires qui ne seront pas libérés / libérés même après qu'ils soient hors de portée.

L'exemple suivant montre un bon exemple de modèle IDisposable avec du code et des commentaires.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        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.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing 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. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}
CharithJ
la source
1

Le cas d'utilisation le plus justifié pour l'élimination des ressources gérées est la préparation du GC à récupérer des ressources qui autrement ne seraient jamais collectées.

Un bon exemple est les références circulaires.

Bien qu'il soit préférable d'utiliser des modèles qui évitent les références circulaires, si vous vous retrouvez avec (par exemple) un objet `` enfant '' qui a une référence à son `` parent '', cela peut arrêter la collecte GC du parent si vous abandonnez simplement la référence et compter sur GC - plus si vous avez implémenté un finaliseur, il ne sera jamais appelé.

La seule façon de contourner ce problème consiste à rompre manuellement les références circulaires en définissant les références parent à null sur les enfants.

Implémenter IDisposable sur les parents et les enfants est le meilleur moyen de le faire. Lorsque Dispose est appelé sur le parent, appelez Dispose sur tous les enfants et dans la méthode enfant Dispose, définissez les références parent sur null.

boîtier de commande
la source
4
Pour la plupart, le GC ne fonctionne pas en identifiant les objets morts, mais plutôt en identifiant les objets vivants. Après chaque cycle gc, pour chaque objet qui a été enregistré pour finalisation, est stocké sur le tas d'objets volumineux, ou est la cible d'un live WeakReference, le système vérifie un indicateur qui indique qu'une référence enracinée en direct a été trouvée dans le dernier cycle GC et ajoutera l'objet à une file d'attente d'objets nécessitant une finalisation immédiate, libérera l'objet du tas d'objets volumineux ou invalidera la référence faible. Les références circulaires ne garderont pas les objets en vie s'il n'existe aucune autre référence.
supercat
1

Je vois que beaucoup de réponses se sont déplacées pour parler de l'utilisation d'IDisposable pour les ressources gérées et non gérées. Je suggère cet article comme l'une des meilleures explications que j'ai trouvées sur la façon dont IDisposable doit être utilisé.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Pour la question réelle; si vous utilisez IDisposable pour nettoyer les objets gérés qui prennent beaucoup de mémoire, la réponse courte serait non . La raison en est qu'une fois que vous vous débarrassez d'un IDisposable, vous devez le laisser hors de portée. À ce stade, tous les objets enfants référencés sont également hors de portée et seront collectés.

La seule véritable exception à cela serait si vous avez beaucoup de mémoire liée aux objets gérés et que vous avez bloqué ce thread en attendant qu'une opération soit terminée. Si ces objets ne sont plus nécessaires une fois cet appel terminé, la définition de ces références sur null peut permettre au garbage collector de les collecter plus tôt. Mais ce scénario représenterait un mauvais code qui devait être refactorisé - pas un cas d'utilisation d'IDisposable.

MikeJ
la source
1
Je n'ai pas compris pourquoi somehone a mis -1 à votre réponse
Sebastian Oscar Lopez