Generics

Parameter tipe, batasan where T, dan metode generik.

Daftar isi

generics memungkinkan satu kelas atau satu metode ditulis sekali untuk banyak tipe data, dengan pemeriksaan tipe tetap dijalankan penuh saat kompilasi. Tanpa generics, wadah umum harus bertipe object dan setiap pemakaian memerlukan konversi manual yang bisa gagal saat program berjalan. Topik ini membahas parameter tipe pada kelas, batasan tipe yang ditulis dengan kata kunci where, serta metode generik yang tipenya ditebak oleh kompilator (compiler).

Parameter tipe pada kelas#

Parameter tipe (type parameter) ditulis di antara tanda kurung siku setelah nama kelas, misalnya Kotak<T>. Huruf T adalah nama tempat yang nantinya diganti tipe nyata saat kelas dipakai, sehingga Kotak<int> dan Kotak<string> merupakan dua tipe terpisah yang sama-sama dibangun dari satu deklarasi. Properti Isi bertipe T, sehingga kompilator mengetahui persis tipe isinya pada setiap pemakaian dan menolak penugasan bertipe salah sejak tahap kompilasi.

csharp Program.cs
// Program demonstrasi kelas generik dan metode generik
using System.Numerics;

Kotak<int> kotakAngka = new Kotak<int>(42);
kotakAngka.Tampilkan();

Kotak<string> kotakTeks = new Kotak<string>("belajar generics");
kotakTeks.Tampilkan();

Console.WriteLine($"Maks(3, 7) = {Maks(3, 7)}");
Console.WriteLine($"Maks(\"apel\", \"jeruk\") = {Maks("apel", "jeruk")}");

CatatanRata<double> catatan = new CatatanRata<double>(12.5, 9.75);
Console.WriteLine($"Rata-rata = {catatan.RataRata()}");

static T Maks<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) >= 0 ? a : b;
}

class Kotak<T>
{
    public Kotak(T isi)
    {
        Isi = isi;
    }

    public T Isi { get; set; }

    public void Tampilkan()
    {
        Console.WriteLine($"Isi kotak: {Isi}");
    }
}

class CatatanRata<T> where T : INumber<T>
{
    public CatatanRata(T angkaPertama, T angkaKedua)
    {
        AngkaPertama = angkaPertama;
        AngkaKedua = angkaKedua;
    }

    public T AngkaPertama { get; }
    public T AngkaKedua { get; }

    public T RataRata()
    {
        return (AngkaPertama + AngkaKedua) / T.CreateChecked(2);
    }
}
text Keluaran
Isi kotak: 42
Isi kotak: belajar generics
Maks(3, 7) = 7
Maks("apel", "jeruk") = jeruk
Rata-rata = 11.125

Untuk menjalankan program tersebut dari folder topik, gunakan perintah berikut.

bash
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
# jalankan program contoh dari folder topik
dotnet run --file Program.cs
text Keluaran
Isi kotak: 42
Isi kotak: belajar generics
Maks(3, 7) = 7
Maks("apel", "jeruk") = jeruk
Rata-rata = 11.125

Keluaran menunjukkan satu deklarasi yang melayani dua tipe berbeda tanpa penulisan ulang maupun konversi. Kotak<int> menyimpan angka dan Kotak<string> menyimpan teks, sedangkan properti Isi pada masing-masing tipe sudah bertipe tepat. Pendekatan ini jauh lebih aman daripada menyimpan nilai dalam object, karena penugasan bertipe salah ditolak kompilator, sedangkan pendekatan object baru menampakkan kesalahannya ketika program berjalan.

csharp Contoh: penugasan string ke Kotak<int> yang ditolak kompilator
// tidak-dijalankan
// Contoh: penugasan string ke properti int menghasilkan error CS0029
Kotak<int> kotak = new Kotak<int>(42);
kotak.Isi = "teks";

class Kotak<T>
{
    public Kotak(T isi)
    {
        Isi = isi;
    }

    public T Isi { get; set; }
}

Kompilator melaporkan error CS0029, yaitu pesan bahwa tipe string tidak dapat dikonversi secara diam-diam menjadi int. Perlindungan semacam ini menjadi alasan utama keberadaan generics, karena kelas koleksi bawaan .NET seperti List<T> dan Dictionary<TKey, TValue> memakai mekanisme yang sama untuk menjamin setiap elemen bertipe yang dijanjikan.

Constraint dengan where#

Parameter tipe tanpa batasan hanya menjamin perilaku paling dasar, yaitu menerima penugasan dan dicetak. constraint adalah batasan yang menyatakan syarat tipe yang boleh menggantikan parameter tipe, ditulis dengan kata kunci where setelah deklarasi kelas atau metode. Constraint membuka kemampuan tambahan di dalam kode generik, karena kompilator mengetahui bahwa setiap T yang diterima pasti memiliki anggota yang dijanjikan batasannya.

Bentuk constraintMakna
where T : structT wajib tipe nilai
where T : classT wajib tipe referensi
where T : new()T wajib memiliki konstruktor tanpa parameter
where T : IComparable<T>T wajib mengimplementasikan antarmuka (interface) IComparable<T>
where T : INumber<T>T wajib mengimplementasikan antarmuka numerik INumber<T>
where T : NamaKelasT wajib kelas tersebut atau turunannya

Metode Maks<T> pada program memakai where T : IComparable<T>, sehingga di dalam metodenya panggilan CompareTo tersedia dan pembandingan dua nilai dapat dilakukan untuk tipe apa pun yang mendukungnya, baik angka maupun teks. Kelas CatatanRata<T> memakai where T : INumber<T>, yaitu antarmuka numerik yang menjanjikan operator penjumlahan dan pembagian, sehingga metode RataRata dapat menghitung tanpa peduli apakah T bertipe double, int, atau decimal. Lebih dari satu constraint ditulis berurutan dipisahkan koma, misalnya where T : class, IComparable<T>.

csharp Contoh: constraint new() menuntut konstruktor tanpa parameter
// Contoh: constraint new() menuntut konstruktor tanpa parameter
Titik titik = BuatBaru<Titik>();
Console.WriteLine($"titik = {titik}");

static T BuatBaru<T>() where T : new()
{
    return new T();
}

class Titik
{
    public int X { get; set; }
    public int Y { get; set; }

    public override string ToString() => $"({X}, {Y})";
}
text Keluaran
titik = (0, 0)

Constraint new() menjanjikan bahwa T dapat dibuat tanpa argumen, sehingga ekspresi new T() sah di dalam metode generiknya. Tanpa constraint tersebut, kompilator menolak new T() karena tidak ada jaminan tipe mana pun yang menggantikan T memiliki konstruktor tanpa parameter. Keluarannya berupa Titik berisi nol, yaitu nilai default kedua propertinya.

Metode generik#

Metode generik mendeklarasikan parameter tipenya sendiri tanpa perlu menjadi bagian kelas generik. Metode Maks<T> pada program dideklarasikan langsung di berkas program dan memakai constraint IComparable<T> untuk membuka kemampuan pembandingan. Pemanggilannya tidak perlu menulis tipe secara eksplisit, karena kompilator menebak T dari tipe argumen, yaitu mekanisme yang disebut type inference, sehingga Maks(3, 7) otomatis menjadi Maks<int> dan Maks("apel", "jeruk") menjadi Maks<string>.

Penulisan tipe secara eksplisit tetap dimungkinkan apabila diperlukan, misalnya Maks<int>(3, 7), dan menjadi wajib ketika argumen tidak cukup untuk menentukan tipenya. Metode generik paling berguna ketika logikanya identik untuk semua tipe yang memenuhi constraint-nya, seperti pembandingan, penghitungan jumlah, atau pencarian terbesar. Apabila logikanya berbeda per tipe, penulisan ulang atau pembebanan metode yang berbeda menjadi pilihan yang lebih jujur daripada memaksakan satu bentuk generik.

Kesalahan umum#

  • Menghitung dengan operator aritmetika pada parameter tipe tanpa constraint numerik. Ekspresi a + b ditolak kompilator untuk T tanpa batasan, dan constraint INumber<T> adalah cara yang tepat untuk membuka operator tersebut.
  • Memakai object sebagai wadah umum alih-alih generics. Pendekatan object menuntut konversi manual pada setiap pembacaan, dan konversi yang salah baru terlihat saat program berjalan sebagai error, bukan saat kompilasi.
  • Menambah constraint yang tidak dibutuhkan. Setiap constraint membatasi tipe yang boleh memakai kode generik tersebut, sehingga daftar constraint perlu dijaga tetap minimal.

Ringkasan#

  • Parameter tipe seperti Kotak<T> memungkinkan satu deklarasi melayani banyak tipe dengan pemeriksaan tipe penuh saat kompilasi, menggantikan pola object yang rawan error saat program berjalan.
  • Constraint yang ditulis dengan where menuntut syarat pada T dan membuka kemampuan tambahan, misalnya IComparable<T> untuk membandingkan dan INumber<T> untuk berhitung.
  • Metode generik memuat parameter tipenya sendiri, dan kompilator menebak tipenya dari argumen melalui type inference sehingga pemanggilan tetap ringkas.

Referensi#