when - idisposable implementation c#




Utilisation correcte de l'interface IDisposable (12)

Il ne devrait pas y avoir d'autres appels aux méthodes d'un objet après que Dispose a été appelé (bien qu'un objet devrait 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 ignoré. Ainsi, l'utilisateur doit simplement ignorer toutes les références à cet objet entier (les mettre à null) et tous les objets connexes internes à lui seront automatiquement nettoyés.

En ce qui concerne la question générale sur la gestion / non gérée 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 sortir de cet état. Maintenant, dans l'exemple typique, le premier pourrait être une fonction qui retourne un handle de fichier, et le second pourrait être un appel à CloseHandle .

Mais - et c'est la clé - ils pourraient être n'importe quelle paire de fonctions. L'un construit un état, l'autre l'arrache. Si l'état a été créé mais pas encore supprimé, 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 y a deux sortes: 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 modifications d'état qui peuvent être librement entrelacées ou doivent être parfaitement imbriquées. Les changements d'état peuvent être threadsafe, ou ils pourraient ne pas.

Regardez l'exemple dans la question de Justice. Les modifications de l'indentation du fichier journal doivent être parfaitement imbriquées ou tout va mal. En outre, ils sont peu susceptibles d'être threadsafe.

Il est possible de faire un tour avec le garbage collector pour nettoyer vos ressources non managé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 façon que ce soit. L'exemple d'une ressource de Justice ne doit donc PAS avoir de finaliseur! Cela n'aiderait personne.

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

Vous devez ensuite utiliser l'instruction using pour avoir une chance de vous assurer que Dispose est appelé. C'est essentiellement comme faire un tour avec la pile (de même que le finaliseur est au GC, l' using est à la pile).

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

Il y a une alternative, que je préfère pour les états qui s'emboîtent 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 résister à l'ajout d'un finaliseur à chaque classe 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 comme si vous faisiez votre propre structure de contrôle pour servir le même but que l' using , sauf que vous n'avez plus aucun danger que l'appelant en abuse. Il n'y a aucun moyen qu'ils peuvent échouer à nettoyer la ressource.

Cette technique est moins utile si la ressource est susceptible de chevaucher des durées de vie, car vous voulez alors pouvoir créer la ressource A, puis la ressource B, puis détruire la ressource A et ensuite la ressource B. Vous ne pouvez pas faire cela si vous avez forcé l'utilisateur à nicher parfaitement comme ça. Mais alors vous devez utiliser IDisposable (mais toujours sans finalizer, sauf si vous avez implémenté threadsafety, ce qui n'est pas gratuit).

Je sais à la lecture de la documentation MSDN que l'utilisation "primaire" de l'interface IDisposable consiste à nettoyer les ressources non managées.

Pour moi, "non géré" signifie des choses comme les connexions de bases de données, sockets, poignées de fenêtres, etc. Mais, j'ai vu du code où la méthode Dispose() est implémentée pour libérer les ressources gérées , ce qui me semble redondant puisque le garbage collector devrait prends soin de ça pour toi.

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, est-ce que cela rend la mémoire libre de garbage collector utilisée par MyCollection plus rapide que normalement?

edit : Jusqu'à présent, les gens ont posté de bons exemples d'utilisation d'IDisposable pour nettoyer les ressources non managées telles que les connexions à la base de données et les bitmaps. Mais supposons que _theList dans le code ci-dessus contenait un million de chaînes, et que vous vouliez libérer cette mémoire maintenant , plutôt que d'attendre le garbage collector. Est-ce que le code ci-dessus accomplirait cela?


L'objectif du modèle Dispose est de fournir un mécanisme pour nettoyer les ressources gérées et non gérées et lorsque 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'effectue pas réellement de disposition relative à la disposition, car l'effacement d'une liste n'a aucun impact sur la disposition de cette collection. De même, les appels pour mettre les variables à zéro n'ont pas non plus d'impact sur le GC.

Vous pouvez jeter un oeil à cet article pour plus de détails sur la façon d'implémenter le pattern Dispose, mais il ressemble à 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 qui est la plus importante ici est le Dispose (bool), qui fonctionne réellement sous deux circonstances différentes:

  • disposer == 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 moteur d'exécution 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 problème de laisser simplement le GC s'occuper 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 pas) afin que les ressources restent autour plus longtemps que nécessaire. Rappelez-vous que l'appel de Dispose () ne provoque pas réellement un cycle de collecte ou n'implique en aucun cas que le GC collecte / libère l'objet; il fournit simplement les moyens de nettoyer de façon plus déterministe les ressources utilisées et indique au GC que ce nettoyage a déjà été effectué.

Le point entier de IDisposable et le modèle de disposition n'est pas de libérer immédiatement la mémoire. Le seul moment où un appel à Dispose aura même une chance de libérer immédiatement de la mémoire est quand il gère le scénario de disposition == false et manipule des ressources non gérées. Pour le code managé, 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 aucun contrôle (autre que d'appeler GC.Collect (), ce que j'ai déjà mentionné n'est pas une bonne idée).

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


Scénarios J'utilise IDisposable: nettoyer les ressources non géré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
}

Si MyCollection doit être collecté de toute façon, vous ne devriez pas avoir à le jeter. Ce faisant, le CPU sera plus que nécessaire et pourra même invalider une analyse pré-calculée que le garbage collector a déjà effectuée.

J'utilise IDisposable pour faire les choses comme s'assurer que les threads sont éliminés correctement, ainsi que les ressources non managées.

EDIT En réponse au commentaire de Scott:

La seule fois où les métriques de performance du GC sont affectées est quand un appel de [GC] Collect () est fait "

Conceptuellement, le GC maintient une vue du graphe de référence d'objet, et toutes les références à partir des trames de pile des fils. Ce tas peut être assez volumineux et s'étendre sur plusieurs pages de mémoire. En tant qu'optimisation, le GC met en cache son analyse des pages qui ont peu de chances de changer très souvent afin d'é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, ainsi il sait 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 hooks n'étaient pas disponibles sous Mac OS X pour l'équipe qui a porté le GC vers Mac afin de faire fonctionner le plug-in Silverlight sur cette plate-forme.

Autre point contre la destruction inutile des ressources: imaginez une situation où un processus est en cours de déchargement. Imaginez aussi que le processus est en cours depuis un certain temps. Les chances sont que beaucoup de 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 ne sert à rien qu'une application en cours de déchargement retourne toutes ces pages de données et de code en mémoire pour libérer les ressources qui seront libérées par le système d'exploitation quand le processus se terminera. Cela s'applique aux ressources gérées et même à certaines ressources non gérées. Seules les ressources qui maintiennent en vie des threads non-background doivent être éliminées, sinon le processus restera actif.

Maintenant, pendant l'exécution normale, il y a des ressources éphémères qui doivent être nettoyées correctement (comme @fezmonkey indique les connexions de base de données, les sockets, les handles de fenêtre ) pour éviter les fuites de mémoire non managées. Ce sont les types de choses qui doivent être éliminées. If you create some class that owns a thread (and by owns I mean that it created it and therefore is responsible for ensuring it stops, at least by my coding style), then that class most likely must implement IDisposable and tear down the thread during Dispose .

The .NET framework uses the IDisposable interface as a signal, even warning, to developers that the this class must be disposed. I can't think of any types in the framework that implement IDisposable (excluding explicit interface implementations) where disposal is optional.


IDisposable is good for unsubscribing from events.


Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties ...


I won't repeat the usual stuff about Using or freeing un-managed resources, that has all been covered. But I would like to point out what seems a common misconception.
Given the following code

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

I realise that the Disposable implementation does not follow current guidelines, but hopefully you all get the idea.
Now, when Dispose is called, how much memory gets freed?

Answer: None.
Calling Dispose can release unmanaged resources, it CANNOT reclaim managed memory, only the GC can do that. Thats not to say that the above isn't a good idea, following the above pattern is still a good idea in fact. Once Dispose has been run, there is nothing stopping the GC re-claiming the memory that was being used by _Large, even though the instance of LargeStuff may still be in scope. The strings in _Large may also be in gen 0 but the instance of LargeStuff might be gen 2, so again, memory would be re-claimed sooner.
There is no point in adding a finaliser to call the Dispose method shown above though. That will just DELAY the re-claiming of memory to allow the finaliser to run.


If anything, I'd expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it...


In the example you posted, it still doesn't "free the memory now". All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You'd have to run some tests to be sure.

The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit call never happened it would then call Rollback on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

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

    tx.Commit();
} 

I've also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

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

So here are a couple of concrete examples that don't do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.


One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.


There are things that the Dispose() operation does in the example code that might have an effect that would not occur due to a normal GC of the MyCollection object.

If the objects referenced by _theList or _theDict are referred to by other objects, then that List<> or Dictionary<> object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.

Of course, if this were the situation I would call it a broken design - I'm just pointing out (pedantically, I suppose) that the Dispose() operation might not be completely redundant, depending on whether there are other uses of the List<> or Dictionary<> that are not shown in the fragment.


Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. If I do this:

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

It's functionally identical to this for purposes of memory management:

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

If you really really really need to free the memory this very instant, call GC.Collect() . There's no reason to do this here, though. The memory will be freed when it's needed.





idisposable