Finalizer (Panduan Pemrograman C#)

Finalizer (secara historis disebut sebagai destruktor) digunakan untuk melakukan pembersihan akhir yang diperlukan ketika instans kelas dikumpulkan oleh pengumpul sampah. Pada kebanyakan kasus, Anda dapat menghindari penulisan finalizer dengan menggunakan kelas System.Runtime.InteropServices.SafeHandle atau turunannya untuk membungkus handel yang tidak terkelola.

Keterangan

  • Finalizer tidak dapat ditentukan dalam struktur. Finalizer hanya digunakan dengan kelas.
  • Sebuah kelas hanya dapat memiliki satu finalizer.
  • Finalizer tidak dapat diwariskan atau kelebihan beban.
  • Finalizer tidak dapat dipanggil. Finalizer dipanggil secara otomatis.
  • Finalizer tidak mengambil pengubah atau memiliki parameter.

Misalnya, berikut ini adalah deklarasi finalizer untuk kelas Car.

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

Finalizer juga dapat diimplementasikan sebagai definisi isi ekspresi, seperti yang ditunjukkan dalam contoh berikut.

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

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

Finalizer secara implisit memanggil Finalize pada kelas dasar objek. Oleh karena itu, panggilan ke finalizer secara implisit diterjemahkan ke kode berikut:

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

Rancangan ini berarti bahwa metode Finalize dipanggil secara rekursif untuk semua instans dalam rantai pewarisan, dari yang paling banyak diturunkan hingga yang paling sedikit diturunkan.

Catatan

Finalizer kosong tidak boleh digunakan. Saat kelas berisi finalizer, entri dibuat di antrean Finalize. Antrean ini diproses oleh pengumpul sampah. Ketika GC memproses antrean tersebut, GC akan memanggil setiap finalizer. Finalizer yang tidak perlu, termasuk finalizer kosong, finalizer yang hanya memanggil finalizer kelas dasar, atau finalizer yang hanya memanggil metode yang dipancarkan secara kondisional, menyebabkan hilangnya performa yang tidak perlu.

Pemrogram tidak memiliki kontrol atas waktu dipanggilnya finalizer; pengumpul sampah memutuskan waktu pemanggilannya. Pengumpul sampah memeriksa objek yang tidak lagi digunakan oleh aplikasi. Jika pengumpul sampah menganggap objek memenuhi syarat untuk finalisasi, ia akan memanggil finalizer (jika ada) dan mengklaim kembali memori yang digunakan untuk menyimpan objek. Dimungkinkan untuk memaksa pengumpulan sampah dengan memanggil Collect, tetapi sering kali, panggilan ini harus dihindari karena dapat menciptakan permasalahan performa.

Catatan

Dijalankannya Finalizer sebagai bagian dari pengakhiran aplikasi atau tidak merupakan pengaturan khusus untuk setiap implementasi .NET. Ketika aplikasi berakhir, .NET Framework akan melakukan setiap upaya yang wajar untuk memanggil finalizer bagi objek yang belum menjalani pengumpulan sampah, kecuali pembersihan telah ditekan (melalui panggilan ke metode pustaka GC.SuppressFinalize, misalnya). .NET 5 (termasuk .NET Core) dan versi yang lebih baru tidak memanggil finalizer sebagai bagian dari pengakhiran aplikasi. Untuk informasi selengkapnya, lihat masalah GitHub dotnet/csharpstandard #291.

Jika Anda perlu melakukan pembersihan secara andal saat aplikasi keluar, daftarkan penghandel untuk kejadian System.AppDomain.ProcessExit tersebut. Penghandel akan memastikan bahwa IDisposable.Dispose() (atau IAsyncDisposable.DisposeAsync()) telah dipanggil untuk semua objek yang memerlukan pembersihan sebelum aplikasi keluar. Karena Anda tidak dapat memanggil Finalizer secara langsung, dan Anda tidak dapat menjamin bahwa pengumpul sampah memanggil semua finalizer sebelum keluar, Anda harus menggunakan Dispose atau DisposeAsync untuk memastikan sumber daya dibebaskan.

Menggunakan finalizer untuk merilis sumber daya

Secara umum, C# tidak memerlukan manajemen memori pada bagian pengembang yang sama banyaknya dengan bahasa yang tidak menargetkan runtime dengan pengumpulan sampah. Hal ini karena pengumpul sampah .NET secara implisit mengelola alokasi dan perilisan memori untuk objek Anda. Namun, ketika aplikasi Anda merangkum sumber daya yang tidak terkelola, seperti windows, file, dan koneksi jaringan, Anda harus menggunakan finalizer untuk membebaskan sumber daya tersebut. Saat objek memenuhi syarat untuk finalisasi, pengumpul sampah akan menjalankan metode Finalize objek tersebut.

Perilisan sumber daya secara eksplisit

Jika aplikasi Anda menggunakan sumber daya eksternal yang mahal, sebaiknya sediakan cara untuk merilis sumber daya secara eksplisit sebelum pengumpul sampah membebaskan objek. Untuk merilis sumber daya, implementasikan metode Dispose dari antarmuka IDisposable yang melakukan pembersihan yang diperlukan untuk objek. Hal ini dapat sangat meningkatkan performa aplikasi. Bahkan dengan kontrol sumber daya yang eksplisit ini, finalizer menjadi perisai untuk membersihkan sumber daya jika panggilan ke metode Dispose gagal.

Untuk informasi selengkapnya tentang pembersihan sumber daya, lihat artikel berikut ini:

Contoh

Contoh berikut membuat tiga kelas yang menghasilkan rantai pewarisan. Kelas First adalah kelas dasar, Second berasal dari First, dan Third berasal dari Second. Ketiganya memiliki finalizer. Dalam Main, instans kelas yang paling banyak diturunkan dibuat. Output dari kode ini bergantung pada implementasi .NET yang ditargetkan aplikasi:

  • .NET Framework: Output menunjukkan bahwa finalizer untuk ketiga kelas dipanggil secara otomatis ketika aplikasi berakhir, secara berurutan dari yang paling banyak diturunkan hingga yang paling sedikit diturunkan.
  • .NET 5 (termasuk .NET Core) atau versi yang lebih baru: Tidak ada output, karena implementasi .NET ini tidak memanggil finalizer saat aplikasi berakhir.
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.
*/

Spesifikasi bahasa C#

Untuk informasi selengkapnya, lihat bagian Finalizer dari Spesifikasi Bahasa C#.

Lihat juga