Tipe nilai seperti int selalu menyimpan sebuah angka; kita tidak bisa menugaskan null kepadanya. Padahal data bisnis sering punya keadaan belum diisi: stok cadangan belum ditetapkan atau tanggal kirim belum dipilih. Trik memakai nilai sentinel seperti -1 menimbulkan ambiguitas karena angka itu bisa jadi data yang sah. Nullable value types menjawab kebutuhan itu dengan membungkus tipe nilai agar menerima null.

Masalah Tipe Nilai Tanpa Keadaan Kosong #

Variabel int wajib berisi angka, sedangkan kebutuhan nyata menuntut tiga keadaan: berisi angka, berisi nol, dan belum diisi. Sentinel menggabungkan makna angka sah dengan penanda kosong, sehingga pembaca kode harus menebak. Struct Nullable<T> menyelesaikannya dengan menyimpan nilai bersama penanda keberadaan nilai.

Mekanisme Nullable<T> #

Deklarasi int? adalah gula sintaks untuk Nullable<int>. Pembungkus ini menyimpan dua hal: nilai di property Value dan penanda bool di property HasValue. Anotasi T? hanya berlaku untuk tipe nilai; untuk tipe referensi, tanda tanya menjadi nullable reference types pada bab berikutnya. Saat pembungkus berisi nilai di-boxing, runtime men-boxing nilai di dalamnya, bukan struct pembungkusnya.

Contoh: Stok Cadangan Gudang #

csharp
int? stokCadangan = null;
int? stokNol = 0;

Console.WriteLine($"Stok cadangan ada nilai: {stokCadangan.HasValue}");
Console.WriteLine($"Stok nol ada nilai: {stokNol.HasValue}");
Console.WriteLine($"Cadangan + GetValueOrDefault: {stokCadangan.GetValueOrDefault(0)}");

stokCadangan = 40;
int stokWajib = stokCadangan.Value;

Console.WriteLine($"Setelah restok: {stokWajib}");
Console.WriteLine($"Operator ??: {stokCadangan ?? 0}");

object hasilBoxing = stokWajib;
Console.WriteLine($"Boxing nilai 40 menjadi: {hasilBoxing.GetType().Name}");

object? boxingKosong = (int?)null;
Console.WriteLine($"Boxing int? null menghasilkan: {boxingKosong ?? "null"}");

Output yang Dihasilkan:

text
Stok cadangan ada nilai: False
Stok nol ada nilai: True
Cadangan + GetValueOrDefault: 0
Setelah restok: 40
Operator ??: 40
Boxing nilai 40 menjadi: Int32
Boxing int? null menghasilkan: null

Mari kita bedah baris demi baris:

  1. Baris 1 (int? stokCadangan = null;): Compiler membuat Nullable<int> yang kosong, sehingga HasValue bernilai False.
  2. Baris 2 (int? stokNol = 0;): Nol adalah nilai sah di dalam pembungkus, sehingga HasValue bernilai True; keadaan ini berbeda dengan kosong.
  3. Baris 6 (GetValueOrDefault(0)): Method mengembalikan nilai bila ada, atau fallback 0 bila kosong, tanpa risiko exception.
  4. Baris 8-9 (stokCadangan = 40; ... .Value): Penugasan mengisi pembungkus, lalu Value mengambil int di dalamnya untuk variabel stokWajib.
  5. Baris 14-18 (dua baris boxing): Runtime men-boxing int menjadi Int32, sedangkan pembungkus kosong di-boxing menjadi null reference.

Anggota Penting Nullable<T> #

AnggotaPerilaku
HasValueTrue bila pembungkus berisi nilai
ValueMengembalikan nilai; melempar exception bila kosong
GetValueOrDefault(fallback)Mengembalikan nilai atau argumen fallback bila kosong