Aplikasi persediaan sering perlu membungkus item dengan tipe yang beragam: sebuah kotak untuk buku, kotak untuk string nama penerima kado, dan seterusnya. Tanpa generics, kita terpaksa menulis class terpisah untuk setiap tipe, atau bergantung pada type object yang menuntut cast setiap kali nilai diambil. Generic class menyelesaikan masalah tersebut dengan satu definisi class yang menerima type parameter, sehingga compiler dapat memeriksa tipe sejak saat kompilasi.

Masalah Kode Duplikat antar Tipe #

Bayangkan kita menulis KotakBuku, KotakPakaian, dan KotakElektronik secara terpisah. Seluruh logika ketiganya identik, tetapi tipe field dan tipe parameter constructor berbeda, sehingga kode duplikat terus bertambah setiap ada tipe baru. Solusi berbasis object hanya memindahkan masalah: compiler tidak lagi tahu tipe isi kotak, cast salah tipe baru terasa saat program berjalan dalam bentuk InvalidCastException, dan value type ikut terkena biaya boxing. Generic class menghapus kedua kelemahan itu sekaligus.

Mendefinisikan Generic Class #

Deklarasi generic class menambahkan daftar type parameter di dalam tanda < dan > setelah nama class. Type parameter T bertindak sebagai placeholder yang dipakai sebagai tipe field, tipe parameter, dan tipe return:

csharp
public class Kotak<T>
{
    private T isi;

    public Kotak(T isi)
    {
        this.isi = isi;
    }

    public T Ambil()
    {
        return isi;
    }
}

public class Buku
{
    public string Judul = "";
}

Buku novel = new Buku { Judul = "Cerita Kota Bandung" };
Kotak<Buku> kotakBuku = new Kotak<Buku>(novel);

Kotak<string> kotakKado = new Kotak<string>("Kado untuk Siti");

Console.WriteLine(kotakBuku.Ambil().Judul);
Console.WriteLine(kotakKado.Ambil());

Output yang Dihasilkan:

text
Cerita Kota Bandung
Kado untuk Siti

Mari kita bedah baris demi baris:

  1. public class Kotak<T>: Nama class diikuti type parameter T; di titik ini T belum merujuk tipe nyata, melainkan nama placeholder milik deklarasi.
  2. private T isi;: Field penyimpan bertipe T, sehingga tipe isinya mengikuti type argument yang dipilih pemakai class.
  3. public Kotak(T isi): Constructor menerima nilai bertipe T; compiler menuntut argumen bertipe sama dengan type argument.
  4. public T Ambil(): Return type T membuat pemanggil menerima nilai dengan tipe aslinya, tanpa cast.
  5. Kotak<Buku> kotakBuku = new Kotak<Buku>(novel);: Kotak<Buku> adalah constructed type; Buku menggantikan seluruh kemunculan T.
  6. Kotak<string> kotakKado = ...: Definisi class yang sama dipakai ulang untuk tipe berbeda tanpa satu baris kode tambahan.

Keamanan Tipe pada Saat Kompilasi #

Karena kotakBuku bertipe Kotak<Buku>, compiler menolak pernyataan yang mencoba memasukkan string ke dalam kotak buku, dan penulisan kotakBuku.Ambil().Judul lolos pemeriksaan tanpa cast. Pemeriksaan ini terjadi sepenuhnya pada saat kompilasi, bukan saat program berjalan. Dengan demikian, kesalahan tipe tertangkap oleh compiler jauh sebelum aplikasi sampai ke tangan pengguna.

Satu definisi Kotak<T> kini melayani semua tipe, dan setiap instantiasi membawa jaminan tipe yang diperiksa compiler.