web-dev-qa-db-fra.com

Pourquoi appeler dispose (faux) dans le destructeur?

Ce qui suit est un exemple typique de modèle d'élimination:

 public bool IsDisposed { get; private set; }

  #region IDisposable Members

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

  protected virtual void Dispose(bool disposing)
  {
    if (!IsDisposed)
    {
      if (disposing)
      {
        //perform cleanup here
      }

      IsDisposed = true;
    }
  }

  ~MyObject()
  {
    Dispose(false);
  }

Je comprends ce que fait disposer, mais ce que je ne comprends pas, c'est pourquoi vous voudriez appeler disposer (faux) dans le destructeur? Si vous regardez la définition, cela ne ferait absolument rien, alors pourquoi quelqu'un pourrait-il écrire du code comme celui-ci? Ne serait-il pas logique de simplement pas appeler se débarrasser du destructeur?

60
ryeguy

Le finaliseur est utilisé comme solution de rechange si l'objet n'est pas éliminé correctement pour une raison quelconque. Normalement, la méthode Dispose() est appelée, ce qui supprime le raccordement du finaliseur et transforme l'objet en un objet géré normal que le garbage collector peut facilement supprimer.

Voici un exemple de MSDN d'une classe qui a des ressources gérées et non gérées à nettoyer.

Notez que les ressources gérées ne sont nettoyées que si disposing est vrai, mais que les ressources non managées sont toujours nettoyées.

public class MyResource: IDisposable
{
    // Pointer to an external unmanaged resource.
    private IntPtr handle;
    // Other managed resource this class uses.
    private Component component = new Component();
    // Track whether Dispose has been called.
    private bool disposed = false;

    // The class constructor.
    public MyResource(IntPtr handle)
    {
        this.handle = handle;
    }

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    private void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if(!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if(disposing)
            {
                // Dispose managed resources.
                component.Dispose();
            }

            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            // only the following code is executed.
            CloseHandle(handle);
            handle = IntPtr.Zero;

            // Note disposing has been done.
            disposed = true;

        }
    }

    // Use interop to call the method necessary
    // to clean up the unmanaged resource.
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~MyResource()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }
}
44
Guffa

"L'idée ici est que Dispose (Boolean) sait s'il est appelé pour effectuer un nettoyage explicite (le Boolean est vrai) par rapport à un appel en raison d'un garbage collection (le Boolean est faux). Cette distinction est utile car, lorsqu'elle est supprimée explicitement, la méthode Dispose (Boolean) peut exécuter le code en toute sécurité à l'aide de champs de type référence qui font référence à d'autres objets en sachant avec certitude que ces autres objets n'ont pas encore été finalisés ou supprimés. Lorsque le Boolean est faux, la méthode Dispose (Boolean) doit ne pas exécuter de code faisant référence à des champs de type référence, car ces objets peuvent avoir déjà été finalisés. "

Il y a beaucoup plus d'informations dans les "Dispose, Finalization, and Resource Management Design Guidelines" .

Modifier: lien.

18
stuartd

Il n'y a pas de destructeurs en C #. C'est un Finalizer, ce qui est différent.

La distinction est de savoir si vous devez nettoyer les objets gérés ou non. Vous ne voulez pas essayer de les nettoyer dans le finaliseur, car ils peuvent eux-mêmes avoir été finalisés.


Je viens tout juste de regarder la page Destructors du Guide de programmation C #. Cela montre que je me suis trompé dans ma réponse, ci-dessus. En particulier, il existe une différence entre destructeur et finaliseur:

class Car
{
    ~Car()  // destructor
    {
        // cleanup statements...
    }
}

est équivalent à

protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}
9
John Saunders

Je pense que la confusion est due au fait que dans votre exemple, vous ne libérez aucune ressource non gérée. Ceux-ci doivent également être libérés lorsque dispose est appelé via la récupération de place et ils seront libérés en dehors de la vérification de disposing. Voir l'exemple MSDN relatif à libération de ressources non gérées . L'autre chose qui devrait/devrait se produire en dehors de la vérification est un appel à une méthode Dispose de n'importe quelle classe de base.

De l'article cité:

   protected override void Dispose(bool disposing) 
   {
      if (disposing) 
      {
         // Release managed resources.
      }
      // Release unmanaged resources.
      // Set large fields to null.
      // Call Dispose on your base class.
      base.Dispose(disposing);
   }
3
tvanfosson

À l'intérieur du if (suppression), vous êtes censé appeler disposer/fermer sur les objets gérés qui ont des ressources non gérées (par exemple, les connexions à la base de données) .Lorsque le finaliseur est appelé, ces objets ne sont plus accessibles afin que les objets eux-mêmes puissent être finalisés et vous ne le faites pas besoin d'appeler disposer sur eux. De plus, l'ordre de finalisation n'est pas déterminé, vous pouvez donc appeler Dispose sur des objets déjà supprimés.

1
ggf31416

L'exemple suivant montre comment créer une classe de ressources qui implémente l'interface IDisposable: https://msdn.Microsoft.com/en-us/library/System.IDisposable.aspx

Dans la fonction Dispose (bool disposing): Si l'élimination est égale à true, la méthode a été appelée directement ou indirectement par votre code. Les ressources gérées et non gérées peuvent être éliminées. Si l'élimination est égale à false, la méthode a été appelée par le runtime 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.

1