web-dev-qa-db-fra.com

Finaliser vs éliminer

Pourquoi certaines personnes utilisent-elles la méthode Finalize par rapport à la méthode Dispose

Dans quelles situations utiliseriez-vous la méthode Finalize par rapport à la méthode Dispose et vice versa?

188
tush1r

D'autres ont déjà couvert la différence entre Dispose et Finalize (au fait, la méthode Finalize est toujours appelée un destructeur dans la spécification du langage), je vais donc ajouter quelques éléments sur les scénarios dans lesquels la méthode Finalize est pratique.

Certains types encapsulent les ressources disponibles de manière à ce qu’elles soient faciles à utiliser et à éliminer en une seule action. L’usage général est souvent le suivant: ouvrir, lire ou écrire, fermer (Dispose). Cela cadre très bien avec la construction using.

D'autres sont un peu plus difficiles. WaitEventHandles pour les instances ne sont pas utilisés comme ceci car ils sont utilisés pour signaler d'un thread à un autre. La question qui se pose alors est de savoir qui devrait appeler Dispose sur ces derniers? En tant que sauvegarde, les types comme ceux-ci implémentent une méthode Finalize, qui garantit que les ressources sont supprimées lorsque l'instance n'est plus référencée par l'application. 

105
Brian Rasmussen

La méthode de finalisation est appelée lorsque votre objet est collecté et vous n'avez aucune garantie quant à son exécution (vous pouvez le forcer, mais cela affectera les performances).

La méthode Dispose, quant à elle, doit ê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 à une base de données, descripteurs de fichier, etc.) dès que le code est terminé. avec votre objet.

La pratique standard consiste à implémenter IDisposable et Dispose afin que vous puissiez utiliser votre objet dans une déclaration using. Tels que using(var foo = new MyObject()) { }. Et dans votre finaliseur, vous appelez Dispose, juste au cas où le code appelant oubliait de vous en débarrasser.

121
Samuel

Finalize est la méthode d'arrière-plan, appelée par le ramasse-miettes lorsqu'elle 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 (descripteurs de fenêtre, connexions de base de données, etc.) lorsqu'elles ne sont plus nécessaires, au lieu de les laisser indéfiniment maintenues jusqu'à ce que le catalogue global atteigne l'objet.

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

En tant qu'implémenteur d'une classe, si vous détenez des ressources gérées qui doivent être éliminées, vous implémentez Dispose. Si vous possédez des ressources natives, vous implémentez à la fois Dispose et Finalize, et les deux appellent une méthode commune qui libère les ressources natives. Ces idiomes sont généralement combinés via une méthode privée Dispose (méthode de suppression), qui élimine les appels avec true et les appels de finalisation avec false. Cette méthode libère toujours les ressources natives, puis vérifie le paramètre de disposition et, le cas échéant, supprime les ressources gérées et appelle GC.SuppressFinalize.

60
itowlson

Finaliser

  • Les finaliseurs doivent toujours être protected et non public ou private afin que la méthode ne puisse pas être appelée directement à partir du code de l'application et qu'elle puisse simultanément appeler la méthode base.Finalize.
  • Les finaliseurs ne doivent libérer que des ressources non gérées.
  • L'infrastructure ne garantit pas qu'un finaliseur s'exécutera sur une instance donnée.
  • Ne jamais allouer de mémoire dans les finaliseurs ou appeler des méthodes virtuelles à partir des 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 les finaliseurs sur les types de valeur.
  • Ne créez pas de destructeurs vides. En d'autres termes, vous ne devriez jamais définir explicitement un destructeur sauf si votre classe a besoin de nettoyer les ressources non gérées et si vous en définissez un, elle devrait fonctionner. Si, par la suite, vous n'avez plus besoin de nettoyer les ressources non gérées dans le destructeur, supprimez-le complètement.

Disposer

  • Implémentez IDisposable sur chaque type doté d'un finaliseur
  • Assurez-vous qu'un objet est rendu inutilisable après avoir appelé la méthode Dispose. En d'autres termes, évitez d'utiliser un objet après que la méthode Dispose a été appelée.
  • Appelez Dispose sur tous les types IDisposable une fois que vous avez terminé avec eux
  • Autoriser Dispose à être appelé plusieurs fois sans générer d'erreurs.
  • Supprimer les appels ultérieurs au finaliseur dans la méthode Dispose à l'aide de la méthode GC.SuppressFinalize
  • Éviter de créer des types de valeur jetables
  • Évitez de lancer des exceptions à partir de méthodes Dispose

Éliminer/modèle finalisé

  • Microsoft vous recommande d'implémenter Dispose et Finalize lorsque vous travaillez avec des ressources non gérées. L'implémentation Finalize serait exécutée et les ressources seraient toujours libérées lorsque l'objet serait récupéré, même si un développeur avait négligé d'appeler explicitement la méthode Dispose.
  • Nettoyez les ressources non managées dans les méthodes Finalize et Dispose. En outre, appelez la méthode Dispose pour tous les objets .NET que vous avez en tant que composants au sein de cette classe (ayant des ressources non gérées en tant que membre) à partir de la méthode Dispose.
39
GenZiy

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

Dispose est simplement 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 implémenté Finalize, alors GC s'assurera de l'appeler.

24
Bhushan Bhangale

Voici quelques clés du livre MCSD Certification Toolkit (examen 70-483), page 193:

destructor ≈ (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 ni ressources gérées ni ressources non gérées , elle n’a pas besoin de Implémenter IDisposable ou d’avoir un destructeur.

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

Si la classe n'a que des ressources non gérées , elle doit implémenter IDisposable et nécessite un destructeur .__ au cas où le programme n'appelle pas Dispose.

La méthode Dispose doit pouvoir s'exécuter plusieurs fois en toute sécurité. Vous pouvez y parvenir en utilisant une variable Pour savoir si elle a déjà été exécutée auparavant.

La méthode Dispose doit libérer des 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 peut 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.)

Après avoir libéré des ressources, le destructeur doit appeler GC.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 géré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);
    }

    // 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;

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

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

Il y a encore beaucoup à apprendre sur le sujet de Garbage Collection , mais c'est un début.

6
JP Alioto

Le finaliseur est destiné au 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/mémoire, etc. ..

Implémenter correctement un finaliseur est notoirement difficile et devrait être évité autant que possible - la classe SafeHandle (disponible dans .Net v2.0 et versions ultérieures) signifie que vous avez très rarement (si jamais) besoin de mettre en œuvre un finaliseur.

L’interface IDisposable sert 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 chaque fois qu’ils ont fini d’utiliser un objet.

Notez que si vous avez un finaliseur, vous devez également implémenter l'interface IDisposable pour permettre aux utilisateurs de libérer explicitement ces ressources plus rapidement que si l'objet avait été nettoyé.

Voir Mise à jour de DG: Elimination, finalisation et gestion des ressources pour ce que je considère comme le meilleur et le plus complet ensemble de recommandations sur les finaliseurs et IDisposable.

5
Justin

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
    }
  }
1
isxaker

Le résumé est -

  • Vous écrivez un finaliseur pour votre classe si elle fait référence à des ressources Non gérées et vous voulez vous assurer que ces ressources sont gérées Sont libérées lorsqu'une instance de cette classe est effacée automatiquement. Notez que vous ne pouvez pas appeler le finaliseur d’un objet de manière explicite - il est appelé automatiquement par le ramasse-miettes comme et quand il le juge nécessaire.
  • D'autre part, vous implémentez l'interface IDisposable (et En conséquence, vous définissez la méthode Dispose () comme résultat pour votre classe) lorsque votre classe Fait référence à des ressources non gérées, mais vous ne souhaitez pas attendre. le ramasse-miettes à démarrer (qui peut être à tout moment - pas sous le contrôle du programmeur) et qui veut libérer ces ressources en tant que dès que vous avez terminé. Ainsi, vous pouvez explicitement libérer des ressources non gérées en appelant la méthode Dispose () d'un objet.

De plus, une autre différence est que - dans l'implémentation de Dispose (), vous devez également libérer les ressources gérées, alors que cela ne devrait 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 aient déjà été nettoyées avant d'être finalisées.

Pour une classe qui utilise des ressources non gérées, la meilleure pratique consiste à définir les méthodes (la méthode Dispose () et le finaliseur) à utiliser comme solution de secours au cas où un développeur oublierait 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);
     }
1
JBelfort

Les instances de classe encapsulent souvent le contrôle sur des ressources non gérées par le moteur d'exécution, telles que les descripteurs de fenêtre (HWND), les connexions à une 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 de destructeur en C # et Managed Extensions for C++). Le garbage collector appelle cette méthode à un moment donné après qu'il n'y ait plus de référence valide à l'objet . Dans certains cas, vous pouvez vouloir fournir aux programmeurs utilisant un objet la possibilité de libérer explicitement ces ressources externes avant le garbage collector. libère l'objet. Si une ressource externe est rare ou chère, il est possible d'obtenir de meilleures performances si le programmeur libère explicitement les 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'il utilise 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 par le biais de Dispose, vous devez effectuer un nettoyage implicite à l'aide de la méthode Finalize. Finalize fournit une sauvegarde pour empêcher les ressources de fuir de manière permanente si le programmeur ne parvient pas à appeler Dispose.

1
Sanjeev Pundir

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

Le GC appelle la méthode finalize pour récupérer les ressources non gérées (telles que l’opérateur de fichier, l’API Windows, la connexion réseau, la connexion à la base de données), mais le temps n’est pas fixé à l’appel du GC. Cela s'appelle implicitement par GC, cela signifie que nous n’avons pas de contrôle de bas niveau dessus.

Dispose, méthode: nous avons un contrôle de bas niveau dessus comme nous l'appelons depuis le 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.

0
Sheo Dayal Singh