Analisis alur compiler menilai kode dari luar, sedangkan niat desain API terkadang lebih kaya dari yang terlihat: setter yang menolak null, method yang mengisi field sebelum dipakai, atau fungsi Try-prefixed yang menjamin hasil di kondisi tertentu. Atribut nullability dari namespace System.Diagnostics.CodeAnalysis menuliskan niat itu agar compiler menilai pemanggil dengan benar.

Lima Atribut Utama #

AtributPernyataan
[AllowNull]Input boleh null meski tipenya non-nullable
[DisallowNull]Input tidak boleh null meski tipenya nullable
[NotNullWhen(bool)]Parameter out/bool terisi bila return bernilai tertentu
[NotNullIfNotNull(arg)]Return terisi bila argumen bernama terisi
[MemberNotNull(member)]Method menjamin anggota tertentu sudah terisi saat kembali

Contoh Terpadu #

csharp
using System.Diagnostics.CodeAnalysis;

public class Profil
{
    private string nama = "";

    [AllowNull]
    public string Nama
    {
        get => nama;
        set => nama = value ?? "(belum diisi)";
    }

    private string? judul;

    [MemberNotNull(nameof(judul))]
    public void SiapkanJudul()
    {
        judul = "Struk Pesanan";
    }

    public int PanjangJudul()
    {
        SiapkanJudul();
        return judul.Length;
    }
}

public class Resi
{
    [DisallowNull]
    public string? Nomor { get; set; }
}

public class LayananKupon
{
    public static bool CobaAmbil(string produk, [NotNullWhen(true)] out string? kode)
    {
        if (produk == "Kopi Gayo")
        {
            kode = "HEMAT20";
            return true;
        }

        kode = null;
        return false;
    }

    public static string? TandaiSiap([NotNullIfNotNull("kode")] string? kode)
    {
        return kode is null ? null : $"{kode}-SIAP";
    }
}

Profil profil = new Profil();
profil.Nama = null;

Console.WriteLine($"Nama: {profil.Nama}");
Console.WriteLine($"Panjang judul: {profil.PanjangJudul()}");

Resi resi = new Resi();
resi.Nomor = "JX-88231";

Console.WriteLine($"Resi: {resi.Nomor}");

if (LayananKupon.CobaAmbil("Kopi Gayo", out string? kode))
{
    Console.WriteLine($"Kode: {kode.ToUpperInvariant()}");
}

Console.WriteLine($"Status: {LayananKupon.TandaiSiap("ORD-4001")}");

Output yang Dihasilkan:

text
Nama: (belum diisi)
Panjang judul: 13
Resi: JX-88231
Kode: HEMAT20
Status: ORD-4001-SIAP

Mari kita bedah anggota kuncinya:

  1. [AllowNull] pada Nama: Penugasan profil.Nama = null sah di mata compiler, sedangkan setter menggantinya dengan teks bawaan; property tetap terbaca sebagai string non-nullable.
  2. [MemberNotNull(nameof(judul))]: Setelah SiapkanJudul() dipanggil, compiler menganggap judul terisi, sehingga judul.Length di PanjangJudul bebas warning.
  3. [DisallowNull] pada Nomor: Tipe string? mengizinkan pembacaan kosong, tetapi penugasan null dari luar ditegur compiler.
  4. [NotNullWhen(true)] pada CobaAmbil: Di dalam blok if yang lolos, kode dijamin terisi, sehingga kode.ToUpperInvariant() bebas warning.
  5. [NotNullIfNotNull("kode")] pada TandaiSiap: Pemanggil dengan argumen non-nullable menerima return non-nullable tanpa pemeriksaan tambahan.

Warning yang Diredam Atribut #

Probe berikut menugaskan null ke property beranotasi [DisallowNull] dan membaca hasil out tanpa [NotNullWhen] (path project dipangkas):

text
Program.cs(4,14): warning CS8625: Cannot convert null literal to non-nullable reference type.
Program.cs(8,23): warning CS8602: Dereference of a possibly null reference.

CS8625 muncul karena [DisallowNull] menolak null pada input, dan CS8602 muncul karena tanpa [NotNullWhen] compiler tidak menjamin parameter out terisi di dalam blok if.