Rak alat tulis adalah kumpulan barang, sehingga terasa wajar bila kita ingin menaruhnya ke variable yang menampung kumpulan barang. Compiler C# menolak pertukaran semacam itu secara sembarangan karena keamanan tipe, tetapi bahasa menyediakan aturan resmi bernama variance. Dua kata kunci mengaturnya: out untuk covariance dan in untuk contravariance, keduanya ditulis pada deklarasi interface atau delegate generik.

Dua Arah Variance #

Kata kunciNamaPosisi TContoh tipe
outCovarianceHanya outputIEnumerable<out T>, Func<out TResult>
inContravarianceHanya inputAction<in T>, IComparer<in T>

Covariance berlaku ketika T hanya keluar dari tipe, sehingga IEnumerable<AlatTulis> boleh dipakai sebagai IEnumerable<Barang>. Contravariance berlaku ketika T hanya masuk, sehingga kode yang mampu memproses Barang apa pun otomatis mampu memproses AlatTulis.

Contoh Terpadu #

csharp
public class Barang
{
    public string Nama = "";
}

public class AlatTulis : Barang
{
}

List<AlatTulis> rakAlatTulis = new List<AlatTulis>
{
    new AlatTulis { Nama = "Pulpen Hitam" },
    new AlatTulis { Nama = "Pensil 2B" }
};

IEnumerable<Barang> rakBarang = rakAlatTulis;
foreach (Barang barang in rakBarang)
{
    Console.WriteLine($"Barang: {barang.Nama}");
}

Action<Barang> catatLog = barang => Console.WriteLine($"Log: {barang.Nama}");
Action<AlatTulis> catatLogAlatTulis = catatLog;
catatLogAlatTulis(new AlatTulis { Nama = "Penghapus Karet" });

Output yang Dihasilkan:

text
Barang: Pulpen Hitam
Barang: Pensil 2B
Log: Penghapus Karet

Mari kita bedah baris demi baris:

  1. public class AlatTulis : Barang: AlatTulis adalah turunan Barang; hubungan pewarisan ini menjadi dasar variance.
  2. IEnumerable<Barang> rakBarang = rakAlatTulis;: Penugasan ini sah karena List<AlatTulis> mengimplementasikan IEnumerable<AlatTulis>, dan IEnumerable<T> dideklarasikan dengan out; ini covariance.
  3. foreach (Barang barang in rakBarang): Compiler mengizinkan pembacaan elemen sebagai Barang karena elemen hanya keluar dari urutan.
  4. Action<Barang> catatLog = ...: Delegate ini menerima Barang apa pun, termasuk seluruh turunannya.
  5. Action<AlatTulis> catatLogAlatTulis = catatLog;: Penugasan ini sah karena Action<T> dideklarasikan dengan in; delegate yang menangani Barang lebih umum otomatis cocok untuk kebutuhan yang lebih khusus; ini contravariance.

Batasan Variance #

  • Hanya interface dan delegate generik yang mendukung kata kunci out dan in; class generik seperti List<T> selalu invariant.
  • List<AlatTulis> tidak boleh ditugaskan ke List<Barang>, karena List juga menulis lewat Add; bila diperbolehkan, string bisa masuk ke daftar yang dijanjikan hanya berisi AlatTulis.
  • Type parameter out tidak boleh muncul sebagai tipe parameter method, dan type parameter in tidak boleh muncul sebagai return type.

Dengan variance, kontrak generik seperti IEnumerable<T> dan Action<T> menyatu mulus dengan hierarki pewarisan.