Constructor adalah kode yang berjalan saat kamu membuat object dengan new. Namanya sama dengan nama class, tidak punya tipe kembalian, dan tugasnya adalah membuat object baru berada di keadaan yang valid sebelum ada yang memakainya.
Output:
Mug: 8.50
Rejected: price
Karena constructor-nya menolak harga negatif, tidak ada Product dengan harga negatif yang bisa ada. Jaminan inilah alasan utama menulis constructor alih-alih mengisi field dari luar.
Default constructor, dan kapan ia hilang
Kalau sebuah class tidak mendeklarasikan constructor, compiler menyediakan constructor public tanpa parameter yang tidak melakukan apa pun selain menjalankan field initializer. Itulah sebabnya new BankAccount() berfungsi pada class yang hanya berisi field.
Begitu kamu mendeklarasikan constructor apa pun, constructor implisit itu tidak dibuat lagi:
class Product
{
public string Name;
public Product(string name) { Name = name; }
}
var p = new Product(); // error CS7036: There is no argument given that corresponds to the required parameter 'name'
Hal ini sering mengejutkan saat sebuah serializer, ORM, atau constraint generic new() membutuhkan constructor tanpa parameter. Kalau kamu ingin keduanya, deklarasikan sendiri constructor tanpa parameternya:
public Product() { } // or
public Product() : this("Unnamed") { } // delegate to the other one (next section)
Constructor bisa public, internal, protected, atau private. Tingkat aksesnya menentukan siapa yang boleh membuat object dengan constructor itu.
Overloading dan chaining dengan this(...)
Sebuah class bisa punya beberapa constructor dengan daftar parameter berbeda. Supaya tidak menyalin assignment yang sama ke setiap constructor, rangkai mereka (chaining): : this(...) memanggil constructor lain di class yang sama sebelum body saat ini berjalan.
Output:
new Pizza():
full constructor: medium, classic, 0
size-only constructor body
parameterless constructor body
Result: medium classic with 0 toppings
Rangkaian itu menjalankan constructor yang paling lengkap lebih dulu, lalu kembali ke constructor yang kamu panggil. Taruh pekerjaan utamanya (validasi, assignment) di satu constructor, dan biarkan constructor lain hanya menyediakan nilai default.
Parameter opsional adalah alternatifnya: public Pizza(string size = "medium", string crust = "classic", int toppings = 0) memberi satu constructor yang mencakup ketiga panggilan itu. Chaining tetap lebih cocok saat constructor yang lebih pendek perlu menghitung sesuatu, atau saat mengubah nilai default tidak boleh mengharuskan pemanggil di-compile ulang (nilai default parameter opsional disalin ke kode pemanggil saat compile).
Memanggil constructor base class dengan base(...)
Class turunan tidak mewarisi constructor. Setiap constructor di class turunan harus menjalankan salah satu constructor base class lebih dulu. Kalau kamu tidak menulis apa pun, compiler menyisipkan panggilan ke constructor tanpa parameter milik base class; kalau base class tidak punya constructor seperti itu, kamu harus memilih salah satu dengan : base(...).
Urutan eksekusi semuanya layak dilihat sekali:
Output:
Truck field initializer
Vehicle field initializer
Vehicle constructor body
Truck constructor body
KL-204, 3 axles
Field initializer berjalan lebih dulu, class turunan sebelum base class, lalu body constructor berjalan dari base class ke bawah. Jadi saat body Truck berjalan, Plate sudah terisi. Satu-satunya jebakan di urutan ini: kalau constructor base class memanggil method virtual yang di-override oleh Truck, override itu berjalan sebelum body constructor Truck, dan melihat Axles masih bernilai 0. Hindari memanggil method virtual dari constructor.
Static constructor
Static constructor menginisialisasi tipenya, bukan sebuah object. Constructor ini tidak punya parameter dan access modifier, dan runtime memanggilnya tepat satu kali, sesaat sebelum tipe itu pertama kali dipakai.
Output:
Program started
Loading tax table (runs once)
123.00
119.00
Runtime membuat static constructor thread-safe: meskipun beberapa thread menyentuh tipe itu bersamaan, constructor-nya berjalan satu kali. Kalau ia melempar exception, setiap pemakaian tipe itu sesudahnya akan melempar TypeInitializationException selama proses berjalan, jadi buat tetap sederhana dan bebas dari I/O yang bisa gagal.
Constructor vs object initializer
Object initializer, new Pizza("large") { Toppings = 3 }, bukan constructor kedua. Compiler mengubahnya menjadi "jalankan constructor, lalu assign member-member ini". Pakai masing-masing sesuai keunggulannya:
- Parameter constructor untuk nilai yang wajib ada supaya object valid. Setiap pemanggil harus mengirimkannya, dan constructor bisa memeriksanya.
- Initializer untuk pengaturan opsional yang punya nilai default masuk akal.
var order = new Order(customerId: 42) { Note = "Leave at the door", GiftWrap = true };
Sejak C# 11, property yang ditandai required wajib diisi di initializer, sehingga sintaks initializer mendapat sebagian jaminan yang dimiliki constructor. Hal ini dibahas di halaman property.
Private constructor
Constructor private berarti hanya class itu sendiri yang bisa membuat instance-nya. Ada dua pola yang memakainya: static factory method, tempat class mengendalikan pembuatan object dan bisa mengembalikan object dari cache atau null, dan singleton:
Output:
21
100
Error compiler untuk new Temperature(5m) di luar class adalah CS0122, error yang sama dengan yang dihasilkan access modifier untuk member private mana pun. Factory method yang bernama memecahkan keterbatasan nyata: dua constructor tidak bisa sama-sama menerima satu decimal, tetapi FromCelsius dan FromFahrenheit bisa. Class yang hanya berisi member static (class utilitas) sebaiknya dideklarasikan static, bukan dengan menyembunyikan constructor-nya.
Constructor dengan expression body
Constructor yang body-nya hanya satu pernyataan bisa memakai =>:
public Product(string name) => Name = name;
Dengan tuple, kamu bahkan bisa meng-assign beberapa field dalam satu baris: public Point(int x, int y) => (X, Y) = (x, y);.
Primary constructor (C# 12)
C# 12 memungkinkan class atau struct mendeklarasikan parameter constructor-nya langsung di tipenya. Parameter itu bisa diakses di setiap member:
public class Customer(string name, int id)
{
public string Name { get; } = name; // copy into a property
public string Label => $"#{id} {Name}"; // or read a parameter directly
public Customer(string name) : this(name, 0) { } // other constructors must chain to it
}
Ada dua perbedaan dengan record yang sering mengejutkan. Parameter primary constructor pada class tidak menjadi property public; kamu harus mengeksposnya sendiri. Selain itu, parameter yang dipakai di dalam sebuah member ditangkap ke field tersembunyi yang tetap bisa diubah, jadi name = "x"; di dalam method tetap bisa di-compile. Primary constructor paling cocok untuk dependency injection, saat parameternya adalah service yang hanya dipanggil oleh class itu:
public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
public Order Get(int id) => repo.Find(id);
}
Kesalahan umum
- Kehilangan constructor tanpa parameter. Menambahkan constructor berparameter menghapus constructor implisitnya (CS7036 di setiap
new X()). - Lupa
: base(...)saat base class tidak punya constructor tanpa parameter. Compiler tidak bisa menyisipkan panggilan implisitnya dan melaporkan bahwa tidak ada argumen untuk parameter wajib di constructor base class. - Memanggil method virtual di dalam constructor. Override-nya berjalan sebelum body constructor turunan.
- Pekerjaan berat di dalam constructor. Panggilan jaringan, membaca file, atau apa pun yang lambat membuat object mahal dibuat dan exception sulit ditangani. Pakai static factory method, atau method inisialisasi async, untuk pekerjaan itu.
- Menulis tipe kembalian.
public void Product()adalah method biasa bernamaProduct, dan compiler menolaknya karena sebuah member tidak boleh punya nama yang sama dengan tipe yang melingkupinya (CS0542).
Pertanyaan yang sering diajukan
Apa itu constructor di C#?
Constructor adalah method khusus yang berjalan saat sebuah object dibuat dengan new. Namanya sama dengan nama class dan tidak punya tipe kembalian: public Product(string name) { Name = name; }. Tugasnya adalah membuat object baru berada di keadaan yang valid, biasanya dengan meng-assign field dari parameternya dan menolak input yang salah.
Apakah C# membuat default constructor secara otomatis?
Hanya kalau class tidak mendeklarasikan constructor sama sekali. Dalam kasus itu compiler menambahkan constructor public tanpa parameter yang membiarkan setiap field bernilai sesuai initializer-nya atau nilai default-nya. Begitu kamu menulis constructor apa pun, misalnya yang menerima nama, constructor implisit itu hilang dan new Product() tidak bisa di-compile lagi (error CS7036). Deklarasikan sendiri public Product() { } kalau kamu masih membutuhkannya.
Bagaimana cara memanggil satu constructor dari constructor lain di C#?
Pakai : this(...) setelah daftar parameter constructor: public Product(string name) : this(name, 0m) { }. Constructor tujuannya berjalan lebih dulu, lalu body dari constructor yang kamu panggil. Untuk memanggil constructor milik base class, pakai : base(...) dengan cara yang sama.
Apa itu static constructor di C#?
Constructor yang ditandai static, tanpa parameter dan tanpa access modifier, yang berjalan sekali per tipe sebelum instance pertama dibuat atau member static pertama dipakai. Constructor ini dipakai untuk menginisialisasi field static yang butuh lebih dari initializer satu baris. Kamu tidak bisa memanggilnya sendiri, dan kalau ia melempar exception, tipe itu tidak bisa dipakai lagi selama program berjalan (TypeInitializationException).
Sebaiknya pakai constructor atau object initializer?
Pakai parameter constructor untuk nilai yang wajib ada supaya object valid, karena compiler memaksa setiap pemanggil mengirimkannya dan constructor bisa memeriksanya. Pakai object initializer (new Product("Mug") { Color = "blue" }) untuk pengaturan opsional. Initializer berjalan setelah constructor selesai.
Apa itu primary constructor di C# 12?
Parameter yang ditulis langsung di deklarasi class, class Customer(string name, int id) { ... }, dan tersedia untuk setiap member class itu. Berbeda dengan parameter posisional pada record, parameter ini tidak menjadi property public: ekspos secara eksplisit dengan public string Name => name; atau public string Name { get; } = name;.