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 kunci | Nama | Posisi T | Contoh tipe |
|---|---|---|---|
out | Covariance | Hanya output | IEnumerable<out T>, Func<out TResult> |
in | Contravariance | Hanya input | Action<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 #
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:
Barang: Pulpen Hitam
Barang: Pensil 2B
Log: Penghapus KaretMari kita bedah baris demi baris:
public class AlatTulis : Barang: AlatTulis adalah turunan Barang; hubungan pewarisan ini menjadi dasar variance.IEnumerable<Barang> rakBarang = rakAlatTulis;: Penugasan ini sah karenaList<AlatTulis>mengimplementasikanIEnumerable<AlatTulis>, danIEnumerable<T>dideklarasikan denganout; ini covariance.foreach (Barang barang in rakBarang): Compiler mengizinkan pembacaan elemen sebagai Barang karena elemen hanya keluar dari urutan.Action<Barang> catatLog = ...: Delegate ini menerima Barang apa pun, termasuk seluruh turunannya.Action<AlatTulis> catatLogAlatTulis = catatLog;: Penugasan ini sah karenaAction<T>dideklarasikan denganin; 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
outdanin; class generik sepertiList<T>selalu invariant. List<AlatTulis>tidak boleh ditugaskan keList<Barang>, karenaListjuga menulis lewatAdd; bila diperbolehkan, string bisa masuk ke daftar yang dijanjikan hanya berisi AlatTulis.- Type parameter
outtidak boleh muncul sebagai tipe parameter method, dan type parameterintidak boleh muncul sebagai return type.
Dengan variance, kontrak generik seperti IEnumerable<T> dan Action<T> menyatu mulus dengan hierarki pewarisan.