Sebuah gudang dapat menyimpan banyak jenis isi: rak material menyimpan string nama barang, sedangkan rak penomoran menyimpan int nomor rak. Pemakai kode kita sebaiknya cukup bergantung pada satu jaminan kemampuan yang sama, yaitu menambah item dan mengambil item pertama, tanpa peduli jenis isinya. Generic interface menyatakan jaminan tersebut sebagai kontrak yang menerima type parameter, sehingga satu deklarasi melayani semua tipe.

Mendefinisikan Generic Interface #

Deklarasinya sama seperti interface biasa, tetapi nama interface diikuti type parameter. Type parameter tersebut boleh dipakai pada signature member mana pun:

csharp
public interface IPenyimpanan<T>
{
    void Tambah(T item);

    T AmbilPertama();
}

Interface ini tidak mengatur isi T; deklarasi hanya menjanjikan bahwa Tambah menerima nilai bertipe T dan AmbilPertama mengembalikan nilai bertipe T yang sama. Dengan pola itu, type parameter bekerja sebagai mata uang bersama antara member-member di dalam kontrak. Interface generik juga boleh menyertakan constraint pada type parameternya, misalnya where T : class, dan constraint itu menjadi syarat bagi setiap class implementator maupun pemakai kontrak.

Implementasi pada Generic Class #

Class implementator menentukan bentuk konkrit dari T. Gudang memilih untuk meneruskan type parameter miliknya sendiri, sehingga satu class Gudang<T> menepati kontrak untuk tipe apa pun:

csharp
public class Gudang<T> : IPenyimpanan<T>
{
    private List<T> rak = new List<T>();

    public void Tambah(T item)
    {
        rak.Add(item);
    }

    public T AmbilPertama()
    {
        return rak[0];
    }
}

Gudang<string> gudangMaterial = new Gudang<string>();
gudangMaterial.Tambah("Semen 50 kg");
gudangMaterial.Tambah("Paku 1 kg");
Console.WriteLine(gudangMaterial.AmbilPertama());

Gudang<int> gudangNomorRak = new Gudang<int>();
gudangNomorRak.Tambah(101);
gudangNomorRak.Tambah(102);
Console.WriteLine(gudangNomorRak.AmbilPertama());

IPenyimpanan<string> penyimpanan = gudangMaterial;
Console.WriteLine(penyimpanan.AmbilPertama());

Output yang Dihasilkan:

text
Semen 50 kg
101
Semen 50 kg

Mari kita bedah baris demi baris:

  1. public class Gudang<T> : IPenyimpanan<T>: Type parameter Gudang diteruskan ke kontrak, sehingga Gudang<string> mengimplementasikan IPenyimpanan<string>.
  2. private List<T> rak = ...: List generik menampung item bertipe T; elemen 0 selalu menjadi item pertama.
  3. void Tambah(T item) dan T AmbilPertama(): Kedua member mengisi kontrak IPenyimpanan<T> persis seperti yang dijanjikan.
  4. gudangMaterial.Tambah("Semen 50 kg"): Compiler menolak argumen selain string, karena type argument sudah terkunci pada Gudang<string>.
  5. IPenyimpanan<string> penyimpanan = gudangMaterial;: Variable bertipe interface menampung object implementator; pemanggilan berjalan lewat kontrak, tanpa tahu class Gudang.

Pada contoh di atas, type parameter milik Gudang diteruskan langsung ke interface. Pola lain juga sah: class biasa tanpa type parameter boleh mengimplementasikan instantiasi konkret, misalnya class ArsipKwitansi : IPenyimpanan<string>, sehingga kontrak yang sama melayani class generik maupun class tertutup.

Satu class juga boleh mengimplementasikan beberapa instantiasi sekaligus, misalnya IPenyimpanan<string> dan IPenyimpanan<int>, karena keduanya dianggap kontrak yang berbeda.