Share via


Finalizers (programmeringsguide för C#)

Slutförare (kallas tidigare för destruatorer) används för att utföra eventuell nödvändig slutlig rensning när en klassinstans samlas in av skräpinsamlaren. I de flesta fall kan du undvika att skriva en finalator genom att använda System.Runtime.InteropServices.SafeHandle eller härledda klasser för att omsluta ohanterade handtag.

Kommentarer

  • Finalizers kan inte definieras i structs. De används endast med klasser.
  • En klass kan bara ha en finalizer.
  • Finalizers kan inte ärvas eller överbelastas.
  • Finalizers kan inte anropas. De anropas automatiskt.
  • En finalizer tar inte modifierare eller har parametrar.

Följande är till exempel en deklaration av en finalator för Car klassen.

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

En finalizer kan också implementeras som en uttryckstextdefinition, vilket visas i följande exempel.

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

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

Finalizern anropar Finalize implicit objektets basklass. Därför översätts ett anrop till en finalator implicit till följande kod:

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

Den här designen Finalize innebär att metoden anropas rekursivt för alla instanser i arvskedjan, från den mest härledda till den minst härledda.

Kommentar

Tomma finalizers ska inte användas. När en klass innehåller en finalizer skapas en post i Finalize kön. Den här kön bearbetas av skräpinsamlaren. När GC bearbetar kön anropas varje finalizer. Onödiga finalatorer, inklusive tomma finalatorer, finalatorer som endast anropar basklassens finalator eller finalizers som endast anropar villkorligt avgivna metoder, orsakar en onödig prestandaförlust.

Programmeraren har ingen kontroll över när slutprogrammet anropas; skräpinsamlaren bestämmer när den ska anropas. Skräpinsamlaren söker efter objekt som inte längre används av programmet. Om det anser att ett objekt är berättigat till slutförande anropar det finalizern (om någon) och återtar det minne som används för att lagra objektet. Det går att framtvinga skräpinsamling genom att anropa Collect, men för det mesta bör det här anropet undvikas eftersom det kan skapa prestandaproblem.

Kommentar

Huruvida finalizers körs som en del av programavslut är specifikt för varje implementering av .NET. När ett program avslutas gör .NET Framework allt som är rimligt för att anropa finalizers för objekt som ännu inte har samlats in skräp, såvida inte sådan rensning har undertryckts (genom ett anrop till biblioteksmetoden GC.SuppressFinalize, till exempel). .NET 5 (inklusive .NET Core) och senare versioner anropar inte finalizers som en del av programavslut. Mer information finns i GitHub-problem med dotnet/csharpstandard #291.

Om du behöver utföra rensningen på ett tillförlitligt sätt när ett program avslutas registrerar du en hanterare för System.AppDomain.ProcessExit händelsen. Den hanteraren skulle se till att IDisposable.Dispose() (eller, IAsyncDisposable.DisposeAsync()) har anropats för alla objekt som kräver rensning innan programmet avslutas. Eftersom du inte kan anropa Slutför direkt och du inte kan garantera att skräpinsamlaren anropar alla finalizers före avslut, måste du använda Dispose eller DisposeAsync se till att resurserna frigörs.

Använda finalizers för att frigöra resurser

I allmänhet kräver C# inte lika mycket minneshantering från utvecklarens sida som språk som inte riktar sig mot en körning med skräpinsamling. Det beror på att .NET-skräpinsamlaren implicit hanterar allokering och frigörande av minne för dina objekt. Men när programmet kapslar in ohanterade resurser, till exempel fönster, filer och nätverksanslutningar, bör du använda finalizers för att frigöra dessa resurser. När objektet är berättigat till slutförande kör skräpinsamlaren Finalize -metoden för objektet.

Explicit frisläppning av resurser

Om ditt program använder en dyr extern resurs rekommenderar vi också att du tillhandahåller ett sätt att uttryckligen frigöra resursen innan skräpinsamlaren frigör objektet. Om du vill frigöra resursen implementerar du en Dispose metod från IDisposable gränssnittet som utför den nödvändiga rensningen för objektet. Detta kan avsevärt förbättra programmets prestanda. Även med den här explicita kontrollen över resurser blir slutföraren ett skydd för att rensa resurser om anropet Dispose till metoden misslyckas.

Mer information om hur du rensar resurser finns i följande artiklar:

Exempel

I följande exempel skapas tre klasser som skapar en arvskedja. Klassen First är basklassen, Second härleds från Firstoch Third härleds från Second. Alla tre har finalizers. I Mainskapas en instans av den mest härledda klassen. Utdata från den här koden beror på vilken implementering av .NET som programmet riktar in sig på:

  • .NET Framework: Utdata visar att finalatorerna för de tre klasserna anropas automatiskt när programmet avslutas, i ordning från de mest härledda till de minst härledda.
  • .NET 5 (inklusive .NET Core) eller en senare version: Det finns inga utdata eftersom den här implementeringen av .NET inte anropar finalizers när programmet avslutas.
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.
*/

Språkspecifikation för C#

Mer information finns i avsnittet Finalizers (Slutförare) i C#-språkspecifikationen.

Se även