Pada kode non-generik, tanda tanya memiliki makna tunggal: int? adalah singkatan Nullable<int>. Pada kode generik, makna T? tidak lagi tunggal, karena compiler belum tahu T adalah value type atau reference type. Bahasa C# lalu mengikat makna T? pada constraint yang menyertai deklarasi. Bab ini membedah tiga makna tersebut dan cara menandai hasil generik yang mungkin kosong.

Tiga Makna Anotasi T #

DeklarasiMakna T?
where T : structSingkatan Nullable<T>, misalnya int?
where T : classT boleh berisi null (nullable reference type)
Tanpa constraintT boleh berisi default(T) (sejak C# 9)

Ketiga bentuk itu sama-sama mengumumkan “nilai mungkin kosong”, tetapi mekanisme runtime-nya berbeda: Nullable<T> adalah struct pembungkus sungguhan, sedangkan dua bentuk lainnya hanya anotasi untuk compiler.

Cache dengan Hasil Bisa Kosong #

Class cache menyimpan satu item terakhir. Item belum tentu pernah disimpan, sehingga field dan return value harus diberi tanda mungkin-kosong:

csharp
public class LayananCache<T> where T : class
{
    private T? itemTerakhir;

    public void Simpan(T item)
    {
        itemTerakhir = item;
    }

    public T? Ambil()
    {
        return itemTerakhir;
    }
}

LayananCache<string> cacheNama = new LayananCache<string>();
string? hasil = cacheNama.Ambil();
Console.WriteLine(hasil is null ? "Cache masih kosong" : $"Cache berisi {hasil}");

cacheNama.Simpan("Sesi Budi");
string? hasilKedua = cacheNama.Ambil();
Console.WriteLine(hasilKedua is null ? "Cache masih kosong" : $"Cache berisi {hasilKedua}");

int? stokKopi = 40;
int? stokTeh = null;
Console.WriteLine(stokKopi.HasValue ? $"Stok kopi: {stokKopi.Value}" : "Stok kopi kosong");
Console.WriteLine(stokTeh.HasValue ? $"Stok teh: {stokTeh.Value}" : "Stok teh kosong");

Output yang Dihasilkan:

text
Cache masih kosong
Cache berisi Sesi Budi
Stok kopi: 40
Stok teh kosong

Mari kita bedah baris demi baris:

  1. where T : class + private T? itemTerakhir;: Kombinasi ini menyatakan field boleh berisi null sebelum Simpan dipanggil pertama kali.
  2. public T? Ambil(): Return type mengumumkan hasil mungkin kosong, sehingga pemanggil diingatkan compiler untuk memeriksanya.
  3. hasil is null ? ... : ...: Pola is null memeriksa kekosongan sebelum nilai dipakai; compiler juga menerima jaminan tipe pada cabang yang benar.
  4. int? stokKopi = 40;: Di luar kode generik, int? tetap berarti Nullable<int>; property HasValue dan Value milik struct pembungkus itu.
  5. stokTeh.HasValue: stokTeh berisi null sehingga HasValue bernilai false dan pesan kosong tercetak.

Constraint Pendukung Nullability #

Constraint where T : notnull menyatakan arah sebaliknya: type argument dijamin bukan null. Constraint ini dipakai Dictionary<TKey, TValue> untuk kunci, dan kita telah melihatnya pada class Kamus<TKey, TValue> di bab Type Parameter.

Dengan tiga makna T? yang terikat constraint, API generik dapat mengumumkan keboleh-kosongan secara presisi kepada seluruh pemanggilnya.