Nullable reference types memberi manfaat penuh ketika API yang kita tulis jujur tentang keboleh-kosongan. API yang jujur memudahkan compiler menilai setiap pemanggil, sedangkan API yang menipu tipenya memaksa pemanggil memakai akhiran ! atau menambah pemeriksaan ganda. Bab ini merangkum prinsip desain tersebut beserta contoh terpadunya.

Prinsip Desain yang Null-Safe #

  • Umumkan hasil yang mungkin kosong lewat return type T? atau pola Try dengan parameter out.
  • Jangan mengembalikan null dari anggota yang bertipe non-nullable; janji tipe harus sama dengan perilaku.
  • Wajibkan data penting lewat modifier required alih-alih mengandalkan pemanggil mengisi field kemudian.
  • Cantumkan atribut nullability untuk pola yang tidak terbaca analisis, seperti TryGetValue.

Contoh: Direktori Pelanggan #

csharp
using System.Diagnostics.CodeAnalysis;

public class DirektoriPelanggan
{
    private readonly Dictionary<string, string> data = new Dictionary<string, string>
    {
        ["M-001"] = "Budi Santoso",
        ["M-002"] = "Siti Rahma"
    };

    public bool CobaCari(string nomor, [NotNullWhen(true)] out string? nama)
    {
        return data.TryGetValue(nomor, out nama);
    }

    public string CariAtauDefault(string nomor)
    {
        return data.TryGetValue(nomor, out string? nama) ? nama : "(pelanggan baru)";
    }
}

public class Produk
{
    public required string Nama { get; init; }
    public int Stok { get; init; }
}

DirektoriPelanggan direktori = new DirektoriPelanggan();

if (direktori.CobaCari("M-001", out string? nama))
{
    Console.WriteLine($"Nama tercatat: {nama.Length} karakter");
}

Console.WriteLine(direktori.CariAtauDefault("M-999"));

Produk produk = new Produk { Nama = "Cokelat Lampung", Stok = 25 };

Console.WriteLine($"{produk.Nama}: stok {produk.Stok}");

Output yang Dihasilkan:

text
Nama tercatat: 12 karakter
(pelanggan baru)
Cokelat Lampung: stok 25

Mari kita bedah baris demi baris:

  1. CobaCari(..., out string? nama): Pola Try mengumumkan dua hal sekaligus; [NotNullWhen(true)] menjamin nama terisi bila return true.
  2. data.TryGetValue(nomor, out nama): Implementasi mendelegasikan ke dictionary, sehingga janji atribut konsisten dengan perilaku TryGetValue bawaan.
  3. Console.WriteLine($"... {nama.Length} ..."): Di dalam blok yang lolos, nama dijamin terisi oleh atribut, sehingga akses Length bebas warning.
  4. CariAtauDefault: Alternatif untuk pemanggil yang tidak butuh cabang; kekosongan diselesaikan di dalam API dengan nilai default yang bermakna.
  5. public required string Nama: Modifier required memaksa object initializer mengisi Nama; object initializer tanpa Nama ditolak compiler dengan CS9035.

Jebakan yang Sering Ditemui #

Property non-nullable tanpa nilai awal memunculkan warning berikut (path project dipangkas):

text
Program.cs(7,19): warning CS8618: Non-nullable property 'NamaProyek' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Pesan CS8618 itu sendiri menyarankan dua jalan keluar: tambahkan modifier required, atau ubah tipe menjadi nullable. Opsi ketiga adalah memberi nilai bawaan pada property, seperti pola = "" pada bab Nullable Annotations.