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 przy użyciu System.Runtime.InteropServices.SafeHandle klas pochodnych lub w celu 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 klasę bazową 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 jest tworzony w kolejce Finalize
. Ta kolejka jest przetwarzana przez moduł odśmiecający elementy. 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 jest wywoływany finalizator; moduł odśmieceń pamięci decyduje, kiedy go wywołać. Moduł odśmiecający ś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 Collectmetody , ale przez większość czasu należy unikać tego wywołania, ponieważ może to powodować problemy z wydajnością.
Uwaga
Określa, czy finalizatory są uruchamiane w ramach kończenia działania aplikacji, jest specyficzne dla 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.SuppressFinalize
biblioteki ). Platforma .NET 5 (w tym .NET Core) i nowsze wersje nie wywołuje finalizatorów w ramach kończenia działania aplikacji. Aby uzyskać więcej informacji, zobacz problem z usługą 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 IDisposable.Dispose() (lub, IAsyncDisposable.DisposeAsync()) została wywołana dla wszystkich obiektów, które wymagają czyszczenia przed zamknięciem aplikacji. Ponieważ nie można wywołać finalizowania bezpośrednio i nie można zagwarantować, że moduł odśmieceń pamięci wywołuje wszystkie finalizatory przed wyjściem, musisz użyć Dispose
lub DisposeAsync
upewnić się, że zasoby są zwolnione.
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ż moduł odśmieceń pamięci platformy .NET niejawnie zarządza alokacją i zwalnianiem pamięci dla 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, moduł odśmiecania pamięci uruchamia Finalize
metodę obiektu.
Jawne wydanie zasobów
Jeśli aplikacja korzysta z kosztownego zasobu zewnętrznego, zalecamy również udostępnienie jawnego zwolnienia zasobu przed zwolnieniem obiektu przez moduł odśmieceń pamięci. 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:
- Oczyszczanie zasobów niezarządzanych
- Implementacja metody Dispose
- Implementowanie metody DisposeAsync
using
wypowiedź
Przykład
Poniższy przykład tworzy trzy klasy tworzące łańcuch dziedziczenia. Klasa jest klasą First
bazową, Second
pochodzi z First
klasy , i Third
pochodzi z Second
klasy . Wszystkie trzy mają finalizatory. W Main
programie 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#.