Udostępnij za pośrednictwem


Finalizers (Przewodnik programowania w języku C#)

Finalizatory (historycznie określane jako destruktory) są używane do wykonywania wszelkich niezbędnych ostatecznych operacji czyszczenia, gdy wystąpienie klasy jest zbierane przez moduł odśmiecania pamięci. W większości przypadków można uniknąć pisania finalizatora, używając System.Runtime.InteropServices.SafeHandle lub klas pochodnych do opakowania dowolnego niezarządzanego uchwytu.

Uwagi

  • Finalizatory nie mogą być zdefiniowane w strukturach. Są one używane tylko z klasami.
  • Klasa może mieć tylko jeden finalizator.
  • Finalizatory nie mogą być dziedziczone ani przeciążone.
  • Nie można wywołać finalizatorów. Są one wywoływane automatycznie.
  • Finalizator nie bierze modyfikatorów ani nie ma parametrów.

Na przykład poniżej znajduje się deklaracja finalizatora Car klasy .

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

Finalizator można również zaimplementować jako definicję treści wyrażenia, jak pokazano w poniższym przykładzie.

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

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

Finalizator niejawnie wywołuje Finalize na klasie bazowej obiektu. W związku z tym wywołanie finalizatora jest niejawnie tłumaczone na następujący kod:

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

Ten projekt oznacza, że Finalize metoda jest wywoływana rekursywnie dla wszystkich wystąpień w łańcuchu dziedziczenia, od najbardziej pochodnych do najmniej pochodnych.

Uwaga

Puste finalizatory nie powinny być używane. Gdy klasa zawiera finalizator, wpis tworzony jest w kolejce Finalize. Ta kolejka jest przetwarzana przez zbieracz śmieci. Gdy GC przetwarza kolejkę, wywołuje każdy finalizator. Niepotrzebne finalizatory, w tym puste finalizatory, finalizatory, które wywołują tylko finalizator klasy bazowej, lub finalizatory, które wywołują tylko metody emitowane warunkowo, powodują niepotrzebną utratę wydajności.

Programista nie ma kontroli nad tym, kiedy finalizator jest wywoływany; odśmiecacz pamięci odpowiada za to, kiedy go wywołać. Zbieracz śmieci sprawdza obiekty, które nie są już używane przez aplikację. Jeśli uzna obiekt kwalifikujący się do finalizacji, wywołuje finalizator (jeśli istnieje) i odzyskuje pamięć używaną do przechowywania obiektu. Istnieje możliwość wymuszenia odzyskiwania pamięci przez wywołanie metody Collect, ale w większości przypadków należy unikać tego wywołania, ponieważ może to powodować problemy z wydajnością.

Uwaga

Czy finalizatory są uruchamiane jako część zakończenia działania aplikacji, zależy od każdej implementacji platformy .NET. Po zakończeniu działania aplikacji program .NET Framework dokłada wszelkich starań, aby wywołać finalizatory obiektów, które nie zostały jeszcze odebrane, chyba że takie czyszczenie zostało pominięte (na przykład przez wywołanie metody GC.SuppressFinalizebiblioteki ). .NET 5 (w tym .NET Core) i nowsze wersje nie wywołują finalizatorów podczas zakończenia działania aplikacji. Aby uzyskać więcej informacji, zobacz zgłoszenie GitHub dotnet/csharpstandard #291.

Jeśli konieczne jest niezawodne czyszczenie po zakończeniu działania aplikacji, zarejestruj procedurę obsługi dla System.AppDomain.ProcessExit zdarzenia. Ta procedura obsługi zapewni, że IDisposable.Dispose() (lub IAsyncDisposable.DisposeAsync()) został wywołany dla wszystkich obiektów, które wymagają czyszczenia przed zamknięciem aplikacji. Ponieważ nie można wywołać Finalize bezpośrednio i nie można zagwarantować, że zbieracz śmieci wywołuje wszystkie finalizatory przed zakończeniem działania, musisz użyć Dispose lub DisposeAsync, aby upewnić się, że zasoby zostaną uwolnione.

Używanie finalizatorów do wydawania zasobów

Ogólnie rzecz biorąc, język C# nie wymaga tak dużego zarządzania pamięcią ze strony dewelopera, jak języków, które nie są przeznaczone dla środowiska uruchomieniowego z odzyskiwaniem pamięci. Dzieje się tak, ponieważ kolektor śmieci platformy .NET domyślnie zarządza alokacją i zwalnianiem pamięci dla Twoich obiektów. Jednak gdy aplikacja hermetyzuje niezarządzane zasoby, takie jak okna, pliki i połączenia sieciowe, należy użyć finalizatorów, aby zwolnić te zasoby. Gdy obiekt kwalifikuje się do finalizacji, zbieracz śmieci uruchamia Finalize metodę obiektu.

Ostrzeżenie

Nie należy uzyskiwać dostępu do elementów członkowskich obiektów zarządzanych z poziomu finalizatora. Podczas finalizacji obiekty zarządzane mogą być już usuwane, co czyni je niedostępnymi lub w nieprawidłowym stanie. Uzyskaj dostęp tylko do niezarządzanych zasobów bezpośrednio z finalizatorów.

Jawne wydanie zasobów

Jeśli aplikacja korzysta z kosztownego zasobu zewnętrznego, zalecamy również udostępnienie sposobu na jawne zwolnienie tego zasobu przed zwolnieniem obiektu przez mechanizm garbage collection. Aby zwolnić zasób, zaimplementuj metodę Dispose z interfejsu IDisposable , która wykonuje niezbędne oczyszczanie obiektu. Może to znacznie poprawić wydajność aplikacji. Nawet w przypadku jawnej kontroli nad zasobami finalizator staje się zabezpieczeniem w celu wyczyszczenia zasobów, jeśli wywołanie Dispose metody zakończy się niepowodzeniem.

Aby uzyskać więcej informacji na temat czyszczenia zasobów, zobacz następujące artykuły:

Przykład

Poniższy przykład tworzy trzy klasy tworzące łańcuch dziedziczenia. First jest klasą bazową, Second jest pochodną klasy First, a Third jest pochodną klasy Second. Wszystkie trzy mają finalizatory. W Main tworzone jest wystąpienie najbardziej pochodnej klasy. Dane wyjściowe z tego kodu zależą od implementacji platformy .NET przeznaczonej dla aplikacji:

  • .NET Framework: dane wyjściowe pokazują, że finalizatory dla trzech klas są wywoływane automatycznie po zakończeniu działania aplikacji w kolejności od najbardziej pochodnej do najmniej pochodnej.
  • .NET 5 (w tym .NET Core) lub nowsza wersja: nie ma danych wyjściowych, ponieważ ta implementacja platformy .NET nie wywołuje finalizatorów po zakończeniu działania aplikacji.
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.
*/

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz sekcję Finalizers (Finalizatory) specyfikacji języka C#.

Zobacz też