Finaliser vs éliminer


Réponses:


120

D'autres ont déjà couvert la différence entre Disposeet Finalize(en fait, la Finalizeméthode est toujours appelée destructeur dans la spécification du langage), je vais donc ajouter un peu sur les scénarios où la Finalizeméthode est utile.

Certains types encapsulent les ressources jetables de manière à être faciles à utiliser et à les éliminer en une seule action. L'usage général est souvent le suivant: ouvrir, lire ou écrire, fermer (éliminer). Cela correspond très bien à la usingconstruction.

D'autres sont un peu plus difficiles. WaitEventHandlescar les instances ne sont pas utilisées comme cela car elles sont utilisées pour signaler d'un thread à un autre. La question devient alors qui devrait faire appel Disposeà ceux-ci? À titre de sauvegarde, ces types implémentent une Finalizeméthode qui garantit que les ressources sont supprimées lorsque l'instance n'est plus référencée par l'application.


60
Je ne pouvais pas comprendre cette réponse approuvée. Je veux toujours connaître les différents. Ce que c'est?
Ismael

22
@ Ismael: La plus grande situation où cela Finalizepeut être justifié est quand il y a un certain nombre d'objets qui sont intéressés à garder une ressource vivante, mais il n'y a aucun moyen par lequel un objet qui cesse d'être intéressé par la ressource peut savoir si c'est le le dernier. Dans ce cas, Finalizene se déclenche généralement que lorsque personne ne s'intéresse à l'objet. Le timing lâche de Finalizeest horrible pour les ressources non fongibles telles que les fichiers et les verrous, mais peut convenir pour les ressources fongibles.
supercat

13
+1 à supercat pour un super nouveau mot (pour moi). Le contexte l'a rendu assez clair, mais juste au cas où pour nous autres, voici ce que wikipedia dit: "La fongibilité est la propriété d'un bien ou d'une marchandise dont les unités individuelles sont capables de substitution mutuelle, comme le pétrole brut doux, une entreprise, des obligations, des métaux précieux ou des devises. "
Jon Coombs

5
@ JonCoombs: C'est à peu près vrai, bien qu'il puisse être intéressant de noter que le terme "ressource fongible" est appliqué à des choses qui sont librement substituables jusqu'à ce qu'elles soient acquises et redeviennent librement substituables après la libération ou l'abandon . Si le système a un pool d'objets de verrouillage et que le code en acquiert un qu'il associe à une entité, alors tant que quelqu'un tient qu'une référence à ce verrou dans le but de l'associer à cette entité , ce verrou ne peut pas être remplacé par tout autre. Si tout le code qui se soucie de l'entité gardée abandonne le verrou, cependant ...
supercat

... alors elle redeviendrait librement substituable jusqu'à ce qu'elle soit associée à une autre entité.
supercat

135

La méthode du finaliseur est appelée lorsque votre objet est récupéré et vous n'avez aucune garantie quand cela se produira (vous pouvez le forcer, mais cela nuira aux performances).

La Disposeméthode, d'autre part, est censée être appelée par le code qui a créé votre classe afin que vous puissiez nettoyer et libérer toutes les ressources que vous avez acquises (données non gérées, connexions à la base de données, descripteurs de fichiers, etc.) au moment où le code est terminé. votre objet.

La pratique standard consiste à implémenter IDisposableet à Disposeutiliser votre objet dans un usingétat. Tels que using(var foo = new MyObject()) { }. Et dans votre finaliseur, vous appelez Dispose, juste au cas où le code appelant aurait oublié de vous éliminer.


17
Vous devez être un peu prudent lorsque vous appelez Dispose à partir de votre implémentation Finalize - Dispose peut également disposer des ressources gérées, que vous ne souhaitez pas toucher depuis votre finaliseur, car elles peuvent déjà avoir été finalisées elles-mêmes.
itowlson

6
@itowlson: La vérification de null combinée à l'hypothèse que les objets peuvent être éliminés deux fois (avec un deuxième appel ne faisant rien) devrait être suffisante.
Samuel

7
Le modèle d'IDisposal standard et l'implémentation cachée d'un Dispose (bool) pour gérer la suppression des composants gérés en option semblent répondre à ce problème.
Brody

Il semble qu'il n'y ait aucune raison d'implémenter le destructeur (la méthode ~ MyClass ()) et plutôt d'implémenter et d'appeler toujours la méthode Dispose (). Ou ai-je tort? Quelqu'un pourrait-il me donner un exemple où les deux devraient être mis en œuvre?
dpelisek

66

Finalize est la méthode de backstop, appelée par le garbage collector lorsqu'il récupère un objet. Dispose est la méthode de «nettoyage déterministe», appelée par les applications pour libérer des ressources natives précieuses (poignées de fenêtre, connexions à la base de données, etc.) lorsqu'elles ne sont plus nécessaires, plutôt que de les laisser indéfiniment jusqu'à ce que le GC arrive à l'objet.

En tant qu'utilisateur d'un objet, vous utilisez toujours Dispose. Finalize est pour le GC.

En tant qu'implémenteur d'une classe, si vous détenez des ressources gérées qui doivent être supprimées, vous implémentez Dispose. Si vous détenez des ressources natives, vous implémentez à la fois Dispose et Finalize et appelez toutes les deux une méthode commune qui libère les ressources natives. Ces idiomes sont généralement combinés via une méthode Dispose privée (élimination booléenne), qui Dispose les appels avec true et Finalise les appels avec false. Cette méthode libère toujours les ressources natives, puis vérifie le paramètre de suppression, et si elle est vraie, elle supprime les ressources gérées et appelle GC.SuppressFinalize.



2
Le modèle original recommandé pour les classes qui contenaient un mélange de ressources autonettoyantes ("gérées") et non autonettoyantes ("non gérées") est depuis longtemps obsolète. Un meilleur modèle consiste à envelopper séparément chaque ressource non gérée dans son propre objet géré qui ne contient aucune référence forte à tout ce qui n'est pas nécessaire pour son nettoyage. Tout ce à quoi un objet finalisable détient une référence forte directe ou indirecte aura sa durée de vie GC étendue. L'encapsulation des éléments nécessaires au nettoyage permettra d'éviter de prolonger la durée de vie du GC des éléments qui ne le sont pas.
supercat

2
@JCoombs: Disposec'est bien, et l'implémenter correctement est généralement facile. Finalizeest mauvais, et sa mise en œuvre correcte est généralement difficile. Entre autres choses, parce que le GC s'assurera qu'aucune identité d'objet ne sera jamais "recyclée" tant qu'il y aura une référence à cet objet, il est facile de nettoyer un tas d' Disposableobjets, dont certains peuvent avoir déjà été nettoyés, est aucun problème; toute référence à un objet sur lequel Disposea déjà été appelé restera une référence à un objet sur lequel Disposea déjà été appelé.
supercat

2
@JCoombs: Les ressources non gérées, en revanche, n'ont généralement pas une telle garantie. Si l'objet Fredpossède le descripteur de fichier # 42 et le ferme, le système peut attacher ce même numéro à certains descripteurs de fichiers qui sont attribués à une autre entité. Dans ce cas, le descripteur de fichier # 42 ne ferait pas référence au fichier fermé de Fred, mais au fichier qui était en cours d'utilisation par cette autre entité; car Fredessayer de refermer la poignée # 42 serait désastreux. Il est possible d'essayer de suivre de manière fiable à 100% si un objet non géré a encore été publié. Essayer de garder une trace de plusieurs objets est beaucoup plus difficile.
supercat

2
@JCoombs: Si chaque ressource non managée est placée dans son propre objet wrapper qui ne fait que contrôler sa durée de vie, alors du code extérieur qui ne sait pas si la ressource a été libérée, mais sait qu'elle devrait l'être si elle ne l'a pas déjà été , peut demander en toute sécurité à l'objet wrapper de le libérer; l'objet wrapper saura s'il l'a fait et peut exécuter ou ignorer la demande. Le fait que le GC garantit qu'une référence au wrapper sera toujours une référence valide au wrapper est une garantie très utile .
supercat

43

Finaliser

  • Les finaliseurs doivent toujours être protected, pas publicou privatepour que la méthode ne puisse pas être appelée directement à partir du code de l'application et en même temps, elle peut appeler la base.Finalizeméthode
  • Les finaliseurs doivent libérer uniquement les ressources non gérées.
  • Le cadre ne garantit pas qu'un finaliseur s'exécutera du tout sur une instance donnée.
  • N'allouez jamais de mémoire dans les finaliseurs et n'appelez pas de méthodes virtuelles depuis les finaliseurs.
  • Évitez la synchronisation et le déclenchement d'exceptions non gérées dans les finaliseurs.
  • L'ordre d'exécution des finaliseurs n'est pas déterministe. En d'autres termes, vous ne pouvez pas compter sur un autre objet toujours disponible dans votre finaliseur.
  • Ne définissez pas de finaliseurs sur les types de valeur.
  • Ne créez pas de destructeurs vides. En d'autres termes, vous ne devez jamais définir explicitement un destructeur à moins que votre classe n'ait besoin de nettoyer les ressources non gérées et si vous en définissez une, elle devrait faire un peu de travail. Si, plus tard, vous n'avez plus besoin de nettoyer les ressources non gérées dans le destructeur, supprimez-le complètement.

Disposer

  • Implémentez IDisposablesur chaque type qui a un finaliseur
  • Assurez-vous qu'un objet est rendu inutilisable après avoir appelé la Disposeméthode. En d'autres termes, évitez d'utiliser un objet après avoir Disposeappelé la méthode.
  • Appelez Disposetous les IDisposabletypes une fois que vous en avez fini avec eux
  • Permet Disposed'être appelé plusieurs fois sans générer d'erreurs.
  • Supprimer les appels ultérieurs au finaliseur depuis la Disposeméthode à l'aide de la GC.SuppressFinalizeméthode
  • Évitez de créer des types de valeur jetables
  • Évitez de lever des exceptions à partir des Disposeméthodes internes

Dispose / Finalized Pattern

  • Microsoft vous recommande d'implémenter les deux Disposeet Finalizelorsque vous travaillez avec des ressources non gérées. L' Finalizeimplémentation s'exécuterait et les ressources seraient toujours libérées lorsque l'objet est récupéré, même si un développeur négligeait d'appeler la Disposeméthode explicitement.
  • Nettoyez les ressources non managées dans la Finalizeméthode ainsi que la Disposeméthode. Appelez en outre la Disposeméthode pour tous les objets .NET que vous avez en tant que composants dans cette classe (ayant des ressources non managées comme membre) à partir de la Disposeméthode.

17
J'ai lu cette même réponse partout et je ne comprends toujours pas quel est le but de chacun. Je ne lis que règles après règles, rien de plus.
Ismael

@ Ismael: et aussi l'auteur n'ajoute rien sauf pour copier et coller du texte à partir de MSDN.
Tarik

@tarik Je l'ai déjà appris. J'avais une conception "prometteuse" à ce moment-là.
Ismael

31

Finalize est appelé par le GC lorsque cet objet n'est plus utilisé.

Dispose n'est qu'une méthode normale que l'utilisateur de cette classe peut appeler pour libérer toutes les ressources.

Si l'utilisateur a oublié d'appeler Dispose et si la classe a Finalize implémenté, GC s'assurera qu'il sera appelé.


3
Réponse la plus propre de tous les temps
dariogriffo

19

Il y a quelques clés dans le livre MCSD Certification Toolkit (examen 70-483) page 193:

destructeur ≈ (il est presque égal à)base.Finalize() , le destructeur est converti en une version de substitution de la méthode Finalize qui exécute le code du destructeur puis appelle la méthode Finalize de la classe de base. Ensuite, c'est totalement non déterministe, vous ne pouvez pas savoir quand sera appelé car cela dépend de GC.

Si une classe ne contient aucune ressource gérée et aucune ressource non gérée , elle ne doit pas implémenter IDisposableni avoir de destructeur.

Si la classe n'a que des ressources gérées , elle doit implémenter IDisposablemais ne doit pas avoir de destructeur. (Lorsque le destructeur s'exécute, vous ne pouvez pas être sûr que les objets gérés existent toujours, vous ne pouvez donc pas appeler leurs Dispose()méthodes de toute façon.)

Si la classe n'a que des ressources non gérées , elle doit implémenter IDisposableet a besoin d'un destructeur au cas où le programme n'appelle pas Dispose().

Dispose()La méthode doit pouvoir être exécutée plusieurs fois en toute sécurité. Vous pouvez y parvenir en utilisant une variable pour savoir si elle a été exécutée auparavant.

Dispose()devrait libérer les ressources gérées et non gérées .

Le destructeur ne doit libérer que des ressources non gérées . Lorsque le destructeur s'exécute, vous ne pouvez pas être sûr que les objets gérés existent toujours, vous ne pouvez donc pas appeler leurs méthodes Dispose de toute façon. Ceci est obtenu en utilisant le protected void Dispose(bool disposing)modèle canonique , où seules les ressources gérées sont libérées (supprimées) quand disposing == true.

Après avoir libéré des ressources, Dispose()doit appelerGC.SuppressFinalize , afin que l'objet puisse ignorer la file d'attente de finalisation.

Un exemple d'implémentation pour une classe avec des ressources non managées et gérées:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {
        FreeResources(true);

        // We don't need the destructor because
        // our resources are already freed.
        GC.SuppressFinalize(this);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;
        }
    }
}

2
C'est une belle réponse! Mais je pense que c'est faux: "le destructeur devrait appeler GC.SuppressFinalize". Au lieu de cela, la méthode publique Dispose () ne devrait-elle pas appeler GC.SuppressFinalize? Voir: docs.microsoft.com/en-us/dotnet/api/… L' appel de cette méthode empêche le garbage collector d'appeler Object.Finalize (qui est remplacé par le destructeur).
Ewa

7

99% du temps, vous ne devriez pas vous inquiéter non plus. :) Mais, si vos objets contiennent des références à des ressources non gérées (poignées de fenêtre, poignées de fichier, par exemple), vous devez fournir un moyen pour votre objet géré de libérer ces ressources. Finalize donne un contrôle implicite sur la libération des ressources. Il est appelé par le garbage collector. Dispose est un moyen de donner un contrôle explicite sur une version des ressources et peut être appelé directement.

Il y a beaucoup plus à apprendre sur le sujet de la collecte des ordures , mais c'est un début.


5
Je suis à peu près sûr que plus de 1% des applications C # utilisent des bases de données: où vous devez vous soucier des trucs SQL IDisposable.
Samuel

1
En outre, vous devez implémenter IDisposable si vous encapsulez IDisposables. Ce qui couvre probablement les 1% restants.
Darren Clark

@Samuel: Je ne vois pas ce que les bases de données ont à voir avec ça. Si vous parlez de fermer les connexions, c'est bien, mais c'est une autre affaire. Vous n'avez pas à disposer d'objets pour fermer les connexions en temps opportun.
JP Alioto

1
@JP: Mais le modèle Using (...) le rend beaucoup plus simple à gérer.
Brody

2
D'accord, mais c'est exactement le point. Le modèle d'utilisation masque pour vous l'appel à éliminer.
JP Alioto

6

Le finaliseur est pour le nettoyage implicite - vous devez l'utiliser chaque fois qu'une classe gère des ressources qui doivent absolument être nettoyées, sinon vous perdriez des poignées / de la mémoire, etc.

La mise en œuvre correcte d'un finaliseur est notoirement difficile et doit être évitée autant que possible - le SafeHandle classe (disponible dans .Net v2.0 et supérieur) signifie maintenant que vous avez très rarement (voire jamais) besoin d'implémenter un finaliseur.

le IDisposable interface est destinée au nettoyage explicite et est beaucoup plus couramment utilisée - vous devez l'utiliser pour permettre aux utilisateurs de libérer ou de nettoyer explicitement les ressources lorsqu'ils ont fini d'utiliser un objet.

Notez que si vous avez un finaliseur, vous devez également implémenter l' IDisposableinterface pour permettre aux utilisateurs de libérer explicitement ces ressources plus tôt qu'ils ne le seraient si l'objet était récupéré.

Voir DG Update: Dispose, Finalization, and Resource Management pour ce que je considère être le meilleur et le plus complet des recommandations sur les finaliseurs et IDisposable.


3

Le résumé est -

  • Vous écrivez un finaliseur pour votre classe s'il fait référence à des ressources non managées et vous voulez vous assurer que ces ressources non managées sont libérées lorsqu'une instance de cette classe est automatiquement récupérée . Notez que vous ne pouvez pas appeler explicitement le Finalizer d'un objet - il est appelé automatiquement par le garbage collector au fur et à mesure qu'il le juge nécessaire.
  • D'un autre côté, vous implémentez l'interface IDisposable (et par conséquent définissez la méthode Dispose () comme résultat pour votre classe) lorsque votre classe fait référence à des ressources non managées, mais vous ne voulez pas attendre que le garbage collector se déclenche (qui peut être à tout moment - sans contrôle du programmeur) et souhaitez libérer ces ressources dès que vous avez terminé. Ainsi, vous pouvez explicitement libérer des ressources non managées en appelant la méthode Dispose () d'un objet.

En outre, une autre différence est que dans l'implémentation Dispose (), vous devez également libérer les ressources gérées , alors que cela ne doit pas être fait dans le Finalizer. En effet, il est très probable que les ressources gérées référencées par l'objet ont déjà été nettoyées avant qu'il ne soit prêt à être finalisé.

Pour une classe qui utilise des ressources non managées, la meilleure pratique consiste à définir à la fois - la méthode Dispose () et le Finalizer - à utiliser comme solution de secours au cas où un développeur oublie de supprimer explicitement l'objet. Les deux peuvent utiliser une méthode partagée pour nettoyer les ressources gérées et non gérées: -

class ClassWithDisposeAndFinalize : IDisposable
{
    // Used to determine if Dispose() has already been called, so that the finalizer
    // knows if it needs to clean up unmanaged resources.
     private bool disposed = false;

     public void Dispose()
     {
       // Call our shared helper method.
       // Specifying "true" signifies that the object user triggered the cleanup.
          CleanUp(true);

       // Now suppress finalization to make sure that the Finalize method 
       // doesn't attempt to clean up unmanaged resources.
          GC.SuppressFinalize(this);
     }
     private void CleanUp(bool disposing)
     {
        // Be sure we have not already been disposed!
        if (!this.disposed)
        {
             // If disposing equals true i.e. if disposed explicitly, dispose all 
             // managed resources.
            if (disposing)
            {
             // Dispose managed resources.
            }
             // Clean up unmanaged resources here.
        }
        disposed = true;
      }

      // the below is called the destructor or Finalizer
     ~ClassWithDisposeAndFinalize()
     {
        // Call our shared helper method.
        // Specifying "false" signifies that the GC triggered the cleanup.
        CleanUp(false);
     }

2

Le meilleur exemple que je connaisse.

 public abstract class DisposableType: IDisposable
  {
    bool disposed = false;

    ~DisposableType()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(false);
      }
    }

    public void Dispose()
    {
      if (!disposed) 
      {
        disposed = true;
        Dispose(true);
        GC.SuppressFinalize(this);
      }
    }

    public void Close()
    {
      Dispose();
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing) 
      {
        // managed objects
      }
      // unmanaged objects and resources
    }
  }

2

Différence entre les méthodes Finalize et Dispose en C #.

GC appelle la méthode finalize pour récupérer les ressources non gérées (telles que l'opérarion de fichier, l'api Windows, la connexion réseau, la connexion à la base de données) mais l'heure n'est pas fixe quand GC l'appellerait. Il est appelé implicitement par GC, cela signifie que nous n'avons pas de contrôle de bas niveau dessus.

Méthode Dispose: nous avons un contrôle de bas niveau dessus comme nous l'appelons à partir du code. nous pouvons récupérer les ressources non gérées chaque fois que nous estimons qu'elles ne sont pas utilisables. Nous pouvons y parvenir en implémentant le modèle IDisposal.


1

Les instances de classe encapsulent souvent le contrôle des ressources qui ne sont pas gérées par le runtime, telles que les descripteurs de fenêtre (HWND), les connexions à la base de données, etc. Par conséquent, vous devez fournir à la fois un moyen explicite et implicite de libérer ces ressources. Fournissez un contrôle implicite en implémentant la méthode Finalize protégée sur un objet (syntaxe du destructeur en C # et les extensions gérées pour C ++). Le garbage collector appelle cette méthode à un moment donné après qu'il n'y a plus de références valides à l'objet. Dans certains cas, vous souhaiterez peut-être fournir aux programmeurs utilisant un objet la possibilité de libérer explicitement ces ressources externes avant que le garbage collector ne libère l'objet. Si une ressource externe est rare ou coûteuse, de meilleures performances peuvent être obtenues si le programmeur libère explicitement des ressources lorsqu'elles ne sont plus utilisées. Pour fournir un contrôle explicite, implémentez la méthode Dispose fournie par l'interface IDisposable. Le consommateur de l'objet doit appeler cette méthode lorsqu'elle a terminé d'utiliser l'objet. Dispose peut être appelé même si d'autres références à l'objet sont actives.

Notez que même lorsque vous fournissez un contrôle explicite via Dispose, vous devez fournir un nettoyage implicite à l'aide de la méthode Finalize. Finalize fournit une sauvegarde pour empêcher les ressources de fuir définitivement si le programmeur ne parvient pas à appeler Dispose.


1

La principale différence entre Dispose et Finalize est que:

Disposeest généralement appelé par votre code. Les ressources sont libérées instantanément lorsque vous l'appelez. Les gens oublient d'appeler la méthode, donc la using() {}déclaration est inventée. Lorsque votre programme {}aura terminé l'exécution du code à l'intérieur de , il appellera la Disposeméthode automatiquement.

Finalizen'est pas appelé par votre code. Il est censé être appelé par le garbage collector (GC). Cela signifie que la ressource peut être libérée à tout moment à l'avenir chaque fois que le GC décide de le faire. Lorsque GC fait son travail, il passe par de nombreuses méthodes Finalize. Si vous avez une logique lourde, cela ralentira le processus. Cela peut entraîner des problèmes de performances pour votre programme. Faites donc attention à ce que vous y mettez.

Personnellement, j'écrirais la majeure partie de la logique de destruction dans Dispose. Espérons que cela dissipe la confusion.


-1

Comme nous le savons, l'élimination et la finalisation sont utilisées pour libérer les ressources non gérées .. mais la différence est que la finalisation utilise deux cycles pour libérer les ressources, alors que l'élimination utilise un cycle ..


Dispose libère la ressource immédiatement. Finalize peut ou non libérer la ressource avec un certain degré de rapidité.
supercat

1
Ah, il veut probablement dire que "un objet finalisable doit être détecté deux fois par le GC avant que sa mémoire ne soit récupérée", lisez plus ici: ericlippert.com/2015/05/18/…
aeroson

-4

Pour répondre à la première partie, vous devez fournir des exemples où les gens utilisent une approche différente pour exactement le même objet de classe. Sinon, il est difficile (voire étrange) de répondre.

Quant à la deuxième question, il vaut mieux lire d'abord cette utilisation correcte de l'interface IDisposable qui prétend que

C'est ton choix! Mais choisissez Dispose.

En d'autres termes: le GC ne connaît que le finaliseur (le cas échéant. Aussi appelé destructeur pour Microsoft). Un bon code tentera de nettoyer les deux (finalizer et Dispose).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.