Finalizers (C#-programmeerhandleiding)
Finalizers (historisch aangeduid als destructors) worden gebruikt om eventuele noodzakelijke definitieve opschoning uit te voeren wanneer een klasse-exemplaar wordt verzameld door de garbagecollector. In de meeste gevallen kunt u voorkomen dat u een finalizer schrijft met behulp van de System.Runtime.InteropServices.SafeHandle of afgeleide klassen om een onbeheerde ingang te verpakken.
Opmerkingen
- Finalizers kunnen niet worden gedefinieerd in structs. Ze worden alleen gebruikt met klassen.
- Een klasse kan slechts één finalizer hebben.
- Finalizers kunnen niet worden overgenomen of overbelast.
- Finalizers kunnen niet worden aangeroepen. Ze worden automatisch aangeroepen.
- Een finalizer heeft geen modifiers of parameters.
Het volgende is bijvoorbeeld een declaratie van een finalizer voor de Car
klasse.
class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}
Een finalizer kan ook worden geïmplementeerd als een definitie van de expressietekst, zoals in het volgende voorbeeld wordt weergegeven.
public class Destroyer
{
public override string ToString() => GetType().Name;
~Destroyer() => Console.WriteLine($"The {ToString()} finalizer is executing.");
}
De finalizer roept Finalize impliciet aan op de basisklasse van het object. Daarom wordt een aanroep naar een finalizer impliciet vertaald naar de volgende code:
protected override void Finalize()
{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}
Dit ontwerp betekent dat de Finalize
methode recursief wordt aangeroepen voor alle exemplaren in de overnameketen, van de meest afgeleide tot de minst afgeleide.
Notitie
Lege finalizers mogen niet worden gebruikt. Wanneer een klasse een finalizer bevat, wordt er een vermelding gemaakt in de Finalize
wachtrij. Deze wachtrij wordt verwerkt door de garbagecollector. Wanneer de GC de wachtrij verwerkt, wordt elke finalizer aangeroepen. Onnodige finalizers, waaronder lege finalizers, finalizers die alleen de finalizer van de basisklasse aanroepen of finalizers die alleen voorwaardelijke gegenereerde methoden aanroepen, veroorzaken onnodig verlies van prestaties.
De programmeur heeft geen controle over wanneer de finalizer wordt aangeroepen; de garbagecollector bepaalt wanneer deze moet worden aangeroepen. De garbagecollector controleert op objecten die niet meer door de toepassing worden gebruikt. Als een object wordt beschouwd dat in aanmerking komt voor voltooien, wordt de finalizer aangeroepen (indien aanwezig) en wordt het geheugen vrijgemaakt dat wordt gebruikt om het object op te slaan. Het is mogelijk om garbagecollection af te dwingen door aan te roepen Collect, maar meestal moet deze aanroep worden vermeden omdat er prestatieproblemen kunnen optreden.
Notitie
Of finalizers al dan niet worden uitgevoerd als onderdeel van de beëindiging van de toepassing, is specifiek voor elke implementatie van .NET. Wanneer een toepassing wordt beëindigd, doet .NET Framework alle redelijke inspanningen om finalizers aan te roepen voor objecten die nog geen garbagecollection zijn verzameld, tenzij dergelijke opschoning is onderdrukt (bijvoorbeeld door een aanroep naar de bibliotheekmethode GC.SuppressFinalize
). .NET 5 (inclusief .NET Core) en latere versies roepen geen finalizers aan als onderdeel van de beëindiging van de toepassing. Zie GitHub-probleem dotnet/csharpstandard #291 voor meer informatie.
Als u op betrouwbare wijze moet opschonen wanneer een toepassing wordt afgesloten, registreert u een handler voor de System.AppDomain.ProcessExit gebeurtenis. Deze handler zorgt ervoor dat IDisposable.Dispose() (of, IAsyncDisposable.DisposeAsync()) is aangeroepen voor alle objecten die moeten worden opgeschoond voordat de toepassing wordt afgesloten. Omdat u Finalize niet rechtstreeks kunt aanroepen en u niet kunt garanderen dat de garbagecollector alle finalizers aanroept voordat u afsluit, moet u deze gebruiken Dispose
of DisposeAsync
ervoor zorgen dat resources worden vrijgemaakt.
Finalizers gebruiken om resources vrij te geven
Over het algemeen vereist C# niet zoveel geheugenbeheer voor het deel van de ontwikkelaar als talen die niet gericht zijn op een runtime met garbagecollection. Dit komt doordat de .NET garbagecollection impliciet de toewijzing en vrijgave van geheugen voor uw objecten beheert. Wanneer uw toepassing echter niet-beheerde resources inkapselt, zoals vensters, bestanden en netwerkverbindingen, moet u finalizers gebruiken om deze resources vrij te maken. Wanneer het object in aanmerking komt voor voltooien, voert de garbagecollector de Finalize
methode van het object uit.
Expliciete release van resources
Als uw toepassing gebruikmaakt van een dure externe resource, raden we u ook aan om expliciet de resource vrij te geven voordat de garbagecollector het object vrijgeeft. Als u de resource wilt vrijgeven, implementeert u een Dispose
methode uit de IDisposable interface die de benodigde opschoonactie voor het object uitvoert. Dit kan de prestaties van de toepassing aanzienlijk verbeteren. Zelfs met deze expliciete controle over resources wordt de finalizer een beveiliging om resources op te schonen als de aanroep van de Dispose
methode mislukt.
Zie de volgende artikelen voor meer informatie over het opschonen van resources:
- Onbeheerde resources opschonen
- Een verwijderingsmethode implementeren
- Een Methode DisposeAsync implementeren
using
verklaring
Opmerking
In het volgende voorbeeld worden drie klassen gemaakt die een overnameketen maken. De klasse First
is de basisklasse, Second
is afgeleid van First
en Third
is afgeleid van Second
. Alle drie hebben finalizers. Er Main
wordt een exemplaar van de meest afgeleide klasse gemaakt. De uitvoer van deze code is afhankelijk van de implementatie van .NET waarop de toepassing is gericht:
- .NET Framework: De uitvoer laat zien dat de finalizers voor de drie klassen automatisch worden aangeroepen wanneer de toepassing wordt beëindigd, in volgorde van de meest afgeleide naar de minst afgeleide.
- .NET 5 (inclusief .NET Core) of een latere versie: er is geen uitvoer, omdat deze implementatie van .NET geen finalizers aanroept wanneer de toepassing wordt beëindigd.
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.
*/
C#-taalspecificatie
Zie de sectie Finalizers van de C#-taalspecificatie voor meer informatie.