Les finaliseurs (historiquement appelés destructeurs) sont utilisés pour effectuer le nettoyage final nécessaire lorsqu’une instance de classe est collectée par le récupérateur de mémoire. Dans la plupart des cas, vous pouvez éviter d’écrire un finaliseur à l’aide des classes System.Runtime.InteropServices.SafeHandle ou dérivées pour envelopper tout descripteur non géré.
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 :
Cette conception signifie que la méthode Finalize est appelée de manière récursive pour toutes les instances de la chaîne d’héritage, de la plus dérivée à la moins dérivée.
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 récupérateur de mémoire. 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 des méthodes émises sous conditions, provoquent une perte inutile de performances.
Le programmeur n’a aucun contrôle sur le moment où le finaliseur est appelé ; le récupérateur de mémoire 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 nettoyage de mémoire en appelant Collect, mais la plupart du temps, cet appel doit être évité, car il peut créer des problèmes de performances.
Notes
L'exécution ou non des finaliseurs 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 les finaliseurs des objets qui n’ont pas encore été collectés par la mémoire, à moins que ce nettoyage n'ait é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 le problème GitHub dotnet/csharpstandard #291.
Si vous devez effectuer un nettoyage fiable lors de la sortie d’une application, inscrivez un gestionnaire pour l’événement System.AppDomain.ProcessExit. Ce gestionnaire garantit que IDisposable.Dispose() (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 Finalize directement et que vous ne pouvez pas garantir que le récupérateur de mémoire appelle tous les finaliseurs avant la sortie, vous devez utiliser Dispose ou DisposeAsync pour vous assurer 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 de la part du développeur que les langages qui ne ciblent pas un runtime avec nettoyage de la mémoire. Cela est dû au fait que le récupérateur de mémoire .NET gère implicitement l’allocation et la libération de mémoire pour vos objets. Toutefois, quand votre application encapsule des ressources non géré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 méthode Dispose à 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 méthode Dispose échoue.
Pour plus d’informations sur le nettoyage des ressources, consultez les articles suivants :
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 que l'application cible :
.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é au moins dérivé.
.NET 5 (y compris .NET Core) ou une version ultérieure : il n’existe aucune 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.
*/
La source de ce contenu se trouve sur GitHub, où vous pouvez également créer et examiner les problèmes et les demandes de tirage. Pour plus d’informations, consultez notre guide du contributeur.
Commentaires sur .NET
.NET est un projet open source. Sélectionnez un lien pour fournir des commentaires :
Découvrez comment implémenter des classes à l’aide de techniques avancées telles que des classes statiques, des classes partielles et des initialiseurs d’objets qui peuvent améliorer la lisibilité, la maintenance et l’organisation de votre code.
En savoir plus sur les opérations de boxing et d’unboxing dans la programmation C#. Consultez des exemples de code et affichez des ressources disponibles supplémentaires.
Un constructeur statique en C# initialise des données statiques ou effectue une action une seule fois. Il s’exécute avant que la première instance soit créée ou que les membres statiques soient référencés.
Les classes et méthodes partielles en C# fractionnent la définition d’une classe, d’un struct, d’une interface ou d’une méthode entre plusieurs fichiers sources.
Découvrez comment surcharger un opérateur C# et quels opérateurs C# sont surchargeables. En général, les opérateurs unaires, arithmétiques, d’égalité et de comparaison sont surchargeables.