Foto oleh Brett Jordan via Pexels

Constructor adalah method khusus yang dijalankan secara otomatis setiap kali sebuah object dibuat dari class, biasanya dipakai untuk menginisialisasi nilai awal property agar object selalu berada dalam kondisi valid sejak pertama kali dibuat.

Destructor, di sisi lain, adalah method khusus yang dijalankan oleh garbage collector ketika object tidak lagi dipakai dan akan dibersihkan dari memori, meski pada praktiknya destructor jarang dipakai secara langsung di C# karena ada mekanisme lain yang lebih terkendali seperti IDisposable.

Artikel ini membahas cara membuat constructor dengan berbagai variasi parameter, konsep constructor overloading, serta kapan destructor benar-benar dibutuhkan dibanding alternatif lain yang lebih umum dipakai dalam praktik modern.

Constructor Dasar untuk Inisialisasi Object

Constructor memastikan object selalu diinisialisasi dengan nilai valid

public class Produk { public string Nama { get; set; } public decimal Harga { get; set; } public Produk(string nama, decimal harga) { Nama = nama; Harga = harga; } } Produk p = new Produk("Laptop", 8000000);
Dengan mendefinisikan constructor yang mewajibkan nama dan harga, setiap object Produk yang dibuat dipastikan selalu memiliki kedua nilai tersebut sejak awal, mencegah kemungkinan object berada dalam kondisi tidak lengkap seperti Nama yang masih kosong atau Harga yang masih nol secara default.

Constructor Overloading untuk Fleksibilitas Pembuatan Object

Constructor kedua memanggil constructor pertama dengan this()

public class Produk { public string Nama { get; set; } public decimal Harga { get; set; } public Produk(string nama, decimal harga) { Nama = nama; Harga = harga; } public Produk(string nama) : this(nama, 0) { } } Produk p1 = new Produk("Laptop", 8000000); Produk p2 = new Produk("Produk Baru"); -- Harga otomatis 0
Constructor overloading memungkinkan satu class memiliki beberapa cara berbeda untuk dibuat objectnya, masing-masing menerima kombinasi parameter yang berbeda. Kata kunci this() dipakai untuk memanggil constructor lain dalam class yang sama, menghindari duplikasi logika inisialisasi yang sama di beberapa constructor berbeda.

Default Constructor dan Parameterless Constructor

Default constructor otomatis tersedia jika tidak ada constructor eksplisit

public class Pengaturan { public string Bahasa { get; set; } = "Indonesia"; public bool NotifikasiAktif { get; set; } = true; // Tidak ada constructor eksplisit, C# otomatis // menyediakan default constructor tanpa parameter } Pengaturan p = new Pengaturan();
Jika sebuah class tidak mendefinisikan constructor sama sekali, C# secara otomatis menyediakan default constructor tanpa parameter yang hanya menginisialisasi property sesuai nilai default yang ditentukan. Namun, begitu satu constructor dengan parameter didefinisikan secara eksplisit, default constructor tersebut tidak lagi tersedia kecuali ditambahkan secara manual.

IDisposable sebagai Alternatif Modern Destructor

IDisposable memberi kontrol eksplisit kapan resource dibersihkan

public class KoneksiDatabase : IDisposable { public void Dispose() { Console.WriteLine("Koneksi database ditutup"); } } using (var koneksi = new KoneksiDatabase()) { // pakai koneksi di sini } -- Dispose() otomatis dipanggil saat keluar blok using
Berbeda dengan destructor yang waktu eksekusinya tidak bisa dipastikan karena bergantung pada kapan garbage collector berjalan, IDisposable dengan blok using memberikan kepastian kapan resource seperti koneksi database atau file handle akan dibersihkan, yaitu tepat saat keluar dari blok using tersebut.

Constructor Chaining dengan this() dan base()

Constructor chaining menggunakan this() untuk menghindari duplikasi kode

class Produk { public string Nama; public decimal Harga; public Produk() : this("Tanpa Nama", 0) { } public Produk(string nama, decimal harga) { Nama = nama; Harga = harga; Console.WriteLine($"Produk '{Nama}' dibuat"); } } var p1 = new Produk(); // Output: Produk 'Tanpa Nama' dibuat var p2 = new Produk("Oli Mesin", 85000); // Output: Produk 'Oli Mesin' dibuat

Constructor chaining adalah teknik memanggil satu constructor dari constructor lain dalam class yang sama menggunakan kata kunci this(), seperti pada constructor tanpa parameter Produk() yang meneruskan nilai default ke constructor dengan dua parameter. Teknik ini menghindari duplikasi logika inisialisasi yang sama di beberapa constructor, sehingga jika ada perubahan logika, cukup diubah di satu tempat saja.

Pada konteks inheritance, kata kunci base() digunakan dengan cara serupa namun untuk memanggil constructor milik parent class, dan pemanggilan ini selalu terjadi sebelum isi constructor pada class turunan dijalankan. Jika sebuah class turunan tidak secara eksplisit memanggil base() dengan parameter tertentu, compiler akan otomatis memanggil parameterless constructor milik parent, dan akan menimbulkan compile error jika parent class tidak memiliki constructor tanpa parameter.

Berbeda dengan constructor, destructor pada C# yang dituliskan dengan tanda ~NamaClass tidak dipanggil secara deterministik oleh developer, melainkan dipanggil oleh garbage collector kapan saja objek tersebut dianggap sudah tidak direferensikan lagi. Karena waktu eksekusinya tidak pasti, destructor jarang dipakai untuk melepas resource penting; pola IDisposable dengan using statement jauh lebih umum digunakan untuk tujuan tersebut.

Static Constructor dan Urutan Inisialisasi Field pada Class

Static constructor berjalan tepat sekali, sebelum member static pertama diakses

class KonfigurasiAplikasi { public static readonly string NamaAplikasi; public static int VersiMayor = 1; static KonfigurasiAplikasi() { NamaAplikasi = "SistemBilling"; Console.WriteLine("Static constructor dijalankan"); } } Console.WriteLine(KonfigurasiAplikasi.VersiMayor); Console.WriteLine(KonfigurasiAplikasi.NamaAplikasi); // Output: // Static constructor dijalankan // 1 // SistemBilling

Static constructor adalah constructor khusus tanpa parameter dan tanpa access modifier yang dijalankan tepat satu kali saja oleh CLR, yaitu pada saat pertama kali class tersebut diakses, baik melalui pembuatan instance maupun melalui pengaksesan member static-nya. Pada contoh di atas, meskipun KonfigurasiAplikasi tidak pernah diinstansiasi dengan new, static constructor tetap terpanggil otomatis begitu baris kode pertama yang mengakses member static-nya dieksekusi.

Static constructor sangat berguna untuk inisialisasi static field yang memerlukan logika lebih kompleks dibanding sekadar assignment sederhana, misalnya membaca nilai dari file konfigurasi atau melakukan kalkulasi awal sebelum field tersebut siap dipakai. Berbeda dengan constructor instance biasa yang bisa dipanggil berkali-kali setiap kali objek baru dibuat, static constructor dijamin oleh CLR hanya dijalankan sekali sepanjang siklus hidup aplikasi, sehingga aman dipakai untuk inisialisasi resource yang mahal secara performa.

Urutan inisialisasi static field mengikuti urutan penulisannya dalam kode sebelum static constructor dijalankan, sehingga field seperti VersiMayor yang memiliki nilai default langsung akan diinisialisasi terlebih dahulu sebelum isi static constructor dieksekusi. Memahami urutan ini penting ketika satu static field bergantung pada nilai static field lain, karena referensi ke field yang belum diinisialisasi akan menghasilkan nilai default tipe tersebut, bukan error, yang terkadang menjadi sumber bug yang sulit dilacak. Jika class memiliki lebih dari satu static constructor secara tidak sengaja melalui inheritance berantai, CLR akan menjamin seluruh static constructor pada hierarki tersebut dijalankan tepat sekali dengan urutan dari parent class menuju class turunan sebelum instance pertama benar-benar dipakai.

Interface IDisposable dan Statement using untuk Resource Cleanup

public class KoneksiFile : IDisposable { private readonly StreamWriter _writer; public KoneksiFile(string path) => _writer = new StreamWriter(path); public void Tulis(string teks) => _writer.WriteLine(teks); public void Dispose() { _writer.Dispose(); Console.WriteLine("Resource file sudah dilepas"); } } using (var koneksi = new KoneksiFile("log.txt")) { koneksi.Tulis("Baris pertama"); } // Output: // Resource file sudah dilepas

Interface IDisposable digunakan oleh class yang memegang resource tidak terkelola seperti file handle, koneksi database, atau socket jaringan, yang perlu dilepas secara eksplisit karena garbage collector .NET tidak mengetahui kapan resource eksternal tersebut sebaiknya ditutup, berbeda dengan memori managed biasa yang dibersihkan otomatis. Class yang mengimplementasikan IDisposable wajib menyediakan method Dispose() untuk pembersihan tersebut, biasanya dengan melepas semua resource internal yang dipegangnya satu per satu secara berurutan.

Statement using memastikan method Dispose() dipanggil secara otomatis begitu blok kode selesai dieksekusi, baik berjalan normal maupun karena terjadi exception di tengah jalan, sehingga menghindari kebocoran resource akibat lupa memanggil Dispose() secara manual di setiap kemungkinan jalur keluar method. Pola ini jauh lebih aman dan ringkas dibandingkan mengandalkan try-finally yang ditulis sendiri di setiap tempat pemakaian object yang mengimplementasikan IDisposable, dan sejak C# versi terbaru bahkan tersedia bentuk using declaration tanpa kurung kurawal yang lebih singkat lagi.

Kesalahan Umum yang Sering Terjadi

Mengandalkan Destructor untuk Membersihkan Resource Penting

Destructor dijalankan oleh garbage collector pada waktu yang tidak bisa dipastikan, sehingga resource seperti koneksi database atau file yang perlu ditutup segera sebaiknya memakai IDisposable, bukan mengandalkan destructor yang eksekusinya tertunda.

Lupa Memanggil Constructor Dasar pada Constructor Overload

Tidak memanggil this() pada constructor overload bisa menyebabkan duplikasi logika inisialisasi yang sama ditulis berulang kali di beberapa constructor, meningkatkan risiko inkonsistensi jika salah satu lupa diperbarui saat ada perubahan.

Membuat Terlalu Banyak Constructor Overload

Terlalu banyak variasi constructor overload bisa membingungkan pemanggil tentang constructor mana yang sebaiknya dipakai; pada kasus ini, named parameter atau object initializer sering menjadi alternatif yang lebih jelas.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Constructor overloading memberikan fleksibilitas cara membuat object dengan kombinasi parameter berbeda, namun jika terlalu banyak variasi, justru membingungkan dan sebaiknya dipertimbangkan alternatif seperti builder pattern atau object initializer.

Destructor cocok untuk kasus sangat jarang yang benar-benar butuh pembersihan saat object dihancurkan garbage collector, namun untuk hampir semua kasus pembersihan resource di dunia nyata, IDisposable dengan blok using adalah pilihan yang jauh lebih terkendali dan dapat diprediksi.

Ringkasan

Constructor memastikan object selalu diinisialisasi dalam kondisi valid sejak awal, dengan overloading memberikan fleksibilitas cara pembuatannya. Untuk pembersihan resource, pilih IDisposable dibanding destructor karena memberikan kontrol yang jauh lebih pasti atas kapan proses pembersihan tersebut benar-benar terjadi.

Seri Belajar C# Fundamental:

← Sebelumnya: Method dan Parameter di C#

→ Selanjutnya: Encapsulation dalam OOP C# Secara Mendalam (segera terbit)