Finaliseurs (Guide de programmation C#)

Les finaliseurs (historiquement appelés destructeurs) sont utilisés pour effectuer tout nettoyage final nécessaire lorsqu’une instance de classe est collectée par le garbage collector. Dans la plupart des cas, vous pouvez éviter d’écrire un finaliseur à l’aide des System.Runtime.InteropServices.SafeHandle classes dérivées ou pour encapsuler tout handle non managé.

Remarques

  • Les finaliseurs ne peuvent pas être définis dans des structs. Ils sont utilisés uniquement avec les classes.
  • Une classe ne peut avoir qu’un seul finaliseur.
  • Les finaliseurs ne peuvent pas être hérités ou surchargés.
  • Les finaliseurs ne peuvent pas être appelés. Ils sont appelés automatiquement.
  • Un finaliseur ne prend pas de modificateur et n’a pas de paramètre.

Par exemple, voici une déclaration de finaliseur pour la classe Car.

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

Un finaliseur peut aussi être implémenté en tant que définition de corps d’expression, comme l’illustre l’exemple suivant.

public class Destroyer
{
   public override string ToString() => GetType().Name;

   ~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is executing.");
}

Le finaliseur appelle implicitement Finalize sur la classe de base de l’objet. Ainsi, un appel à un finaliseur est traduit implicitement en ce code :

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

Cette conception signifie que la Finalize méthode est appelée récursivement pour toutes les instances de la chaîne d’héritage, du plus dérivé au moins dérivé.

Notes

Les finaliseurs vides ne doivent pas être utilisés. Quand une classe contient un finaliseur, une entrée est créée dans la file d’attente Finalize. Cette file d’attente est traitée par le garbage collector. Lorsque le GC traite la file d’attente, il appelle chaque finaliseur. Les finaliseurs inutiles, y compris les finaliseurs vides, les finaliseurs qui appellent uniquement le finaliseur de classe de base ou les finaliseurs qui appellent uniquement les méthodes émises conditionnellement, provoquent une perte inutile de performances.

Le programmeur n’a aucun contrôle sur le moment où le finaliseur est appelé; le garbage collector décide quand l’appeler. Le récupérateur de mémoire recherche les objets qui ne sont plus utilisés par l’application. S’il considère qu’un objet peut être finalisé, il appelle le finaliseur (s’il y en a un) et libère la mémoire utilisée pour stocker l’objet. Il est possible de forcer le garbage collection en appelant Collect, mais la plupart du temps, cet appel doit être évité, car il peut créer des problèmes de performances.

Notes

Si les finaliseurs sont exécutés dans le cadre de l’arrêt de l’application est spécifique à chaque implémentation de .NET. Lorsqu’une application se termine, .NET Framework effectue tous les efforts raisonnables pour appeler des finaliseurs pour les objets qui n’ont pas encore été collectés, sauf si ce nettoyage a été supprimé (par un appel à la méthode GC.SuppressFinalizede bibliothèque, par exemple). .NET 5 (y compris .NET Core) et les versions ultérieures n’appellent pas de finaliseurs dans le cadre de l’arrêt de l’application. Pour plus d’informations, consultez GitHub problème dotnet/csharpstandard #291.

Si vous devez effectuer le nettoyage de manière fiable lorsqu’une application quitte, inscrivez un gestionnaire pour l’événement System.AppDomain.ProcessExit . Ce gestionnaire garantit IDisposable.Dispose() que (ou, IAsyncDisposable.DisposeAsync()) a été appelé pour tous les objets qui nécessitent un nettoyage avant la sortie de l’application. Étant donné que vous ne pouvez pas appeler Finaliser directement et que vous ne pouvez pas garantir que le garbage collector appelle tous les finaliseurs avant la sortie, vous devez utiliser Dispose ou DisposeAsync vérifier que les ressources sont libérées.

Utiliser des finaliseurs pour libérer des ressources

En règle générale, C# ne nécessite pas autant de gestion de la mémoire au sein du développeur que les langages qui ne ciblent pas un runtime avec garbage collection. Cela est dû au fait que le garbage collector .NET gère implicitement l’allocation et la mise en production de la mémoire pour vos objets. Toutefois, lorsque votre application encapsule des ressources non managées, telles que des fenêtres, des fichiers et des connexions réseau, vous devez utiliser des finaliseurs pour libérer ces ressources. Quand l’objet peut être finalisé, le récupérateur de mémoire exécute la méthode Finalize de l’objet.

Libération explicite de ressources

Si votre application utilise une ressource externe coûteuse, nous vous recommandons également de proposer un moyen de libérer explicitement la ressource avant que le récupérateur de mémoire ne libère l’objet. Pour libérer la ressource, implémentez une Dispose méthode à partir de l’interface IDisposable qui effectue le nettoyage nécessaire pour l’objet. Cela peut améliorer considérablement les performances de l’application. Même avec ce contrôle explicite sur les ressources, le finaliseur devient une protection pour nettoyer les ressources si l’appel à la Dispose méthode échoue.

Pour plus d’informations sur le nettoyage des ressources, consultez les articles suivants :

Exemple

L’exemple suivant crée trois classes qui forment une chaîne d’héritage. La classe First est la classe de base, Second est dérivée de First, et Third est dérivée de Second. Toutes trois ont des finaliseurs. Dans Main, une instance de la classe la plus dérivée est créée. La sortie de ce code dépend de l’implémentation de .NET cible de l’application :

  • .NET Framework : La sortie indique que les finaliseurs pour les trois classes sont appelés automatiquement lorsque l’application se termine, dans l’ordre du plus dérivé vers le moins dérivé.
  • .NET 5 (y compris .NET Core) ou une version ultérieure : il n’y a pas de sortie, car cette implémentation de .NET n’appelle pas de finaliseurs lorsque l’application se termine.
class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's finalizer is called.");
    }
}

class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's finalizer is called.");
    }
}

class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's finalizer is called.");
    }
}

/* 
Test with code like the following:
    Third t = new Third();
    t = null;

When objects are finalized, the output would be:
Third's finalizer is called.
Second's finalizer is called.
First's finalizer is called.
*/

spécification du langage C#

Pour plus d’informations, consultez la section Finalisers de la spécification du langage C#.

Voir aussi