Quand dois-je utiliser GC.SuppressFinalize ()?

287

Dans .NET, dans quelles circonstances dois-je utiliser GC.SuppressFinalize()?

Quels avantages me procurent cette méthode?

GEOCHET
la source
J'ai vu quelques questions sur les finaliseurs et IDisposable, stackoverflow devrait également avoir quelque chose sur GC.SupressFinalize et les références faibles
Sam Saffron
Je ne pense pas que les références faibles fassent grand-chose pour les finaliseurs - vous devriez peut-être poster une question plus directe à leur sujet.
Michael Burr
Yerp, je voulais publier une question séparée sur les références faibles, tout cela peut être lié lorsque vous créez des pools d'objets. Je devrais également poser une question sur la renaissance de l'objet ala ReRegisterForFinalize
Sam Saffron

Réponses:

296

SuppressFinalizene doit être appelé que par une classe dotée d'un finaliseur. Il informe le Garbage Collector (GC) que l' thisobjet a été entièrement nettoyé.

Le IDisposablemodèle recommandé lorsque vous avez un finaliseur est:

public class MyClass : IDisposable
{
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // called via myClass.Dispose(). 
                // OK to use any private object references
            }
            // Release unmanaged resources.
            // Set large fields to null.                
            disposed = true;
        }
    }

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

    ~MyClass() // the finalizer
    {
        Dispose(false);
    }
}

Normalement, le CLR garde un onglet sur les objets avec un finaliseur lorsqu'ils sont créés (ce qui les rend plus coûteux à créer). SuppressFinalizeindique au GC que l'objet a été correctement nettoyé et n'a pas besoin d'aller dans la file d'attente du finaliseur. Il ressemble à un destructeur C ++, mais n'agit en rien.

L' SuppressFinalizeoptimisation n'est pas anodine, car vos objets peuvent vivre longtemps dans la file d'attente du finaliseur. Ne soyez pas tenté d'appeler SuppressFinalized'autres objets. C'est un grave défaut qui attend de se produire.

Les directives de conception nous informent qu'un finaliseur n'est pas nécessaire si votre objet est implémenté IDisposable, mais si vous avez un finaliseur, vous devez l'implémenter IDisposablepour permettre le nettoyage déterministe de votre classe.

La plupart du temps, vous devriez pouvoir vous en sortir IDisposablepour nettoyer les ressources. Vous ne devez avoir besoin d'un finaliseur que lorsque votre objet conserve des ressources non gérées et vous devez garantir que ces ressources sont nettoyées.

Remarque: Parfois, les codeurs ajoutent un finaliseur pour déboguer les versions de leurs propres IDisposableclasses afin de tester que le code a IDisposablecorrectement disposé leur objet.

public void Dispose() // Implement IDisposable
{
    Dispose(true);
#if DEBUG
    GC.SuppressFinalize(this);
#endif
}

#if DEBUG
~MyClass() // the finalizer
{
    Dispose(false);
}
#endif
Robert Paulson
la source
1
Dans le premier extrait de code, je poste simplement à quoi ressemble le modèle IDisposable + finalizer recommandé. Le débogage du code est bon, mais il peut être gênant. .. Je ne peux que recommander d'éviter les finaliseurs, sauf pour les classes qui ont des ressources non gérées. L'écriture de code de finaliseur sécurisé n'est pas anodine.
Robert Paulson
1
Salut, Pourquoi devons-nous appeler Dispose avec false comme paramètre à partir du finaliseur? Que se passe-t-il si le disposer n'a jamais été appelé et qu'il ne dispose pas? Et si nous vérifions simplement si l'objet a été éliminé ou non et faisons le nettoyage proprement dit.
Dreamer
3
@Dreamer - cela dépend de votre implémentation. En général, vous voulez savoir si Dispose est appelé par le finaliseur par rapport à l'implémentation IDisposable.Dispose (). S'il est appelé depuis le finaliseur, vous devez supposer que les références privées ne sont plus valides et que vous ne pouvez vraiment pas faire grand-chose. Si toutefois appelé depuis IDisposable.Dispose (), vous savez que les références sont toujours valides.
Robert Paulson
32
Si la classe implémentant IDisposablene l'est pas sealed, elle doit inclure l'appel à GC.SuppressFinalize(this) même si elle n'inclut pas de finaliseur défini par l'utilisateur . Cela est nécessaire pour garantir une sémantique correcte pour les types dérivés qui ajoutent un finaliseur défini par l'utilisateur mais remplacent uniquement la Dispose(bool)méthode protégée .
Sam Harwell
1
Ne pas être sealedcomme mentionné par @SamHarwell est important pour les classes dérivées. CodeAnalysis résulte en ca1816 + ca1063 lorsque la classe n'est pas scellée, mais les classes scellées sont bien sans SuppressFinalize.
dashesy
38

SupressFinalizeindique au système que tout travail qui aurait été effectué dans le finaliseur a déjà été fait, il n'est donc pas nécessaire d'appeler le finaliseur. À partir des documents .NET:

Les objets qui implémentent l'interface IDisposable peuvent appeler cette méthode à partir de la méthode IDisposable.Dispose pour empêcher le garbage collector d'appeler Object.Finalize sur un objet qui n'en a pas besoin.

En général, la plupart des Dispose()méthodes devraient pouvoir appeler GC.SupressFinalize(), car elles devraient nettoyer tout ce qui serait nettoyé dans le finaliseur.

SupressFinalizeest juste quelque chose qui fournit une optimisation qui permet au système de ne pas déranger la mise en file d'attente de l'objet sur le thread de finalisation. Un rédacteur Dispose()/ rédacteur correctement écrit devrait fonctionner correctement avec ou sans appel à GC.SupressFinalize().

Michael Burr
la source
2

Cette méthode doit être appelée sur la Disposeméthode des objets qui implémente le IDisposable, de cette façon, le GC n'appellerait pas le finaliseur une autre fois si quelqu'un appelle la Disposeméthode.

Voir: GC.SuppressFinalize (Object), méthode - Microsoft Docs

albertein
la source
9
Je pense que "Must" est faux - pas même "devrait" - C'est juste que dans certains scénarios, vous pouvez éliminer la surcharge de la mise en file d'attente / finalisation de l'objet.
Basic
1
Dispose(true);
GC.SuppressFinalize(this);

Si l'objet a un finaliseur, .net met une référence dans la file d'attente de finalisation.

Puisque nous avons appelé Dispose(ture), il efface l'objet, nous n'avons donc pas besoin de la file d'attente de finalisation pour faire ce travail.

Appelez donc GC.SuppressFinalize(this)remove reference dans la file d'attente de finalisation.

Max CHien
la source
0

Si une classe, ou quelque chose qui en dérive, peut contenir la dernière référence en direct à un objet avec un finaliseur, alors GC.SuppressFinalize(this)ou GC.KeepAlive(this)doit être appelé sur l'objet après toute opération qui pourrait être affectée par ce finaliseur, garantissant ainsi que le finaliseur l'emporte ne fonctionnera qu'après la fin de cette opération.

Le coût de GC.KeepAlive() et GC.SuppressFinalize(this)est essentiellement le même dans toute classe qui n'a pas de finaliseur, et les classes qui ont des finaliseurs devraient généralement appeler GC.SuppressFinalize(this), donc utiliser cette dernière fonction comme dernière étape de Dispose()peut ne pas toujours être nécessaire, mais ce ne sera pas le cas se tromper.

supercat
la source