Meilleures pratiques d'utilisation de Dispose et Finalize dans .Net

Microsoft .Net Framework fournit un garbage collector qui s'exécute en arrière-plan et libère la mémoire occupée par les objets gérés lorsqu'ils ne sont plus référencés dans votre code. Bien que le garbage collector soit habile à nettoyer la mémoire occupée par les objets gérés, il n'est pas garanti que la mémoire occupée par des objets non gérés soit nettoyée lors du prochain cycle GC. Si vous avez des ressources non gérées dans votre application, vous devez vous assurer de libérer ces ressources explicitement lorsque vous avez fini de les utiliser. Dans cet article, je vais mettre en évidence les meilleures pratiques à suivre pour nettoyer les ressources utilisées dans votre application.

Le GC utilise des générations pour maintenir et gérer la durée de vie relative des objets créés dans la mémoire. Les objets créés à nouveau sont placés dans la génération 0. L'hypothèse de base est qu'un objet nouvellement créé peut avoir une durée de vie plus courte tandis qu'un objet ancien peut avoir une durée de vie plus longue. Lorsque les objets résidant dans la génération 0 ne sont pas récupérés après un cycle GC, ils sont déplacés vers la génération 1. De même, si les objets résidant dans la génération 1 survivent à un nettoyage GC, ils sont déplacés vers la génération 2. Notez que le GC s'exécute plus fréquemment dans le générations inférieures que dans les plus élevées. Ainsi, les objets qui résident dans la génération 0 seraient nettoyés plus fréquemment que les objets qui résident dans la génération 1. Ainsi,c'est une meilleure pratique de programmation pour vous assurer que vous utilisez plus d'objets locaux que d'objets dans la portée supérieure pour éviter que les objets ne soient déplacés vers des générations supérieures.

Notez que lorsque vous avez un destructeur dans votre classe, le runtime le traite comme une méthode Finalize (). Comme la finalisation est coûteuse, vous ne devez utiliser des destructeurs que si nécessaire - lorsque vous avez des ressources dans votre classe que vous devrez nettoyer. Lorsque vous avez un finaliseur dans votre classe, les objets de ces classes sont déplacés vers la file d'attente de finalisation. Si les objets sont accessibles, ils sont déplacés vers la file d'attente «Accessible». Le GC récupère la mémoire occupée par les objets qui ne sont pas accessibles. Périodiquement, le GC vérifie si les objets qui résident dans la file d'attente «accessible» sont accessibles. S'ils ne sont pas accessibles, la mémoire occupée par ces objets est récupérée. Ainsi, il est évident que les objets qui résident dans la file d'attente "Freachable" auraient besoin de plus de temps pour être nettoyés par le ramasse-miettes.C'est une mauvaise pratique d'avoir des destructeurs vides dans votre classe C # car les objets de ces classes seraient déplacés vers la file d'attente de finalisation, puis vers la file d'attente "Freachable" si nécessaire.

Un finaliseur est appelé implicitement lorsque la mémoire occupée par l'objet est récupérée. Cependant, il n'est pas garanti qu'un finaliseur soit appelé par le GC - il peut ou non être appelé du tout. En substance, un finaliseur fonctionne sur un mode non déterministe - le runtime ne garantit pas du tout qu'un finaliseur sera appelé. Vous pouvez cependant forcer le finaliseur à être appelé bien que ce ne soit pas du tout une bonne pratique car des pénalités de performance sont associées. Les finaliseurs doivent toujours être protégés et doivent toujours être utilisés pour nettoyer uniquement les ressources gérées. Vous ne devez jamais allouer de mémoire dans le finaliseur, écrire du code pour implémenter la sécurité des threads ou appeler des méthodes virtuelles à partir d'un finaliseur.

La méthode Dispose, quant à elle, fournit une approche de «nettoyage déterministe» pour le nettoyage des ressources dans .Net. Cependant, la méthode Dispose contrairement au finaliseur doit être appelée explicitement. Si vous avez une méthode Dispose définie dans une classe, vous devez vous assurer qu'elle est appelée. Ainsi, la méthode Dispose doit être appelée explicitement par le code client. Mais que faire si vous oubliez d'appeler la méthode Dispose exposée par une classe qui utilise des ressources non managées? Les clients d'une instance d'une classe qui implémente l'interface IDisposable doivent appeler la méthode Dispose explicitement. Dans ce cas, vous devez appeler Dispose depuis le finaliseur. Cette stratégie de finalisation déterministe automatique garantit que les ressources non gérées utilisées dans votre code sont nettoyées.

Vous devez implémenter IDisposable sur chaque type qui a un finaliseur. Il est recommandé d'implémenter à la fois Dispose et Finalize lorsque vous avez des ressources non gérées dans votre classe.

L'extrait de code suivant illustre comment vous pouvez implémenter le modèle Dispose Finalize en C #.

protected virtual void Dispose (suppression des booléens)

        {

            if (élimination)

            {

                // écrire du code pour nettoyer les objets gérés

            }

            // écrire du code pour nettoyer les objets et les ressources non managés

        }

Cette méthode Dispose paramétrée peut être appelée automatiquement à partir du destructeur, comme indiqué dans l'extrait de code ci-dessous.

  ~ Ressources ()

        {

            si (! éliminé)

            {

                disposé = vrai;

                Dispose (faux);

            }

        }