Berikut adalah contoh sederhana menggunakan hierarki warisan.
Dengan hierarki kelas yang sederhana:

Dan dalam kode:
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
Invarian (mis. Parameter tipe umum * tidak * dihiasi inatau outkata kunci)
Tampaknya, metode seperti ini
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
... harus menerima koleksi yang heterogen: (yang memang ada)
var myAnimals = new List<LifeForm>
{
new Giraffe(),
new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra
Namun, melewati koleksi tipe yang lebih diturunkan gagal!
var myGiraffes = new List<Giraffe>
{
new Giraffe(), // "Jerry"
new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!
cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'
Mengapa? Karena parameter generikIList<LifeForm> bukan kovarian -
IList<T>invarian, jadi IList<LifeForm>hanya menerima koleksi (yang mengimplementasikan IList) di mana tipe parameternya Tharus LifeForm.
Jika metode implementasi PrintLifeForms berbahaya (tetapi memiliki tanda tangan metode yang sama), alasan mengapa kompiler mencegah lewat List<Giraffe>menjadi jelas:
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
lifeForms.Add(new Zebra());
}
Karena IListmengizinkan penambahan atau penghapusan elemen, maka setiap subkelas LifeFormdapat ditambahkan ke parameterlifeForms , dan akan melanggar tipe kumpulan dari setiap tipe turunan yang diteruskan ke metode. (Di sini, metode jahat akan berusaha menambahkan Zebrake var myGiraffes). Untungnya, kompiler melindungi kita dari bahaya ini.
Kovarian (Generik dengan tipe parameterisasi yang didekorasi out)
Kovarian secara luas digunakan dengan koleksi yang tidak dapat diubah (yaitu ketika elemen baru tidak dapat ditambahkan atau dihapus dari koleksi)
Solusi untuk contoh di atas adalah untuk memastikan bahwa tipe pengumpulan generik kovarian digunakan, misalnya IEnumerable(didefinisikan sebagai IEnumerable<out T>). IEnumerabletidak memiliki metode untuk mengubah ke koleksi, dan sebagai hasil dari outkovarian, koleksi dengan subtipe LifeFormsekarang dapat diteruskan ke metode:
public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
PrintLifeFormssekarang dapat dipanggil dengan Zebras, Giraffesdan IEnumerable<>sembarang subkelas dariLifeForm
Contravariance (Generic dengan tipe parameterized yang didekorasi dengan in)
Kontravarians sering digunakan ketika fungsi dilewatkan sebagai parameter.
Berikut adalah contoh fungsi, yang menggunakan Action<Zebra>parameter, dan menjalankannya pada turunan Zebra yang dikenal:
public void PerformZebraAction(Action<Zebra> zebraAction)
{
var zebra = new Zebra();
zebraAction(zebra);
}
Seperti yang diharapkan, ini berfungsi dengan baik:
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra
Secara intuitif, ini akan gagal:
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction);
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
Namun, ini berhasil
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal
dan bahkan ini juga berhasil:
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba
Mengapa? Karena Actiondidefinisikan sebagai Action<in T>, yaitu contravariant, artinya untuk Action<Zebra> myAction, yang myActiondapat berupa "sebagian besar" Action<Zebra>, tetapi superclasses yang kurang turunan dariZebra juga dapat diterima.
Meskipun ini mungkin tidak intuitif pada awalnya (misalnya bagaimana bisa Action<object>dilewatkan sebagai parameter yang memerlukan Action<Zebra>?), Jika Anda membongkar langkah-langkahnya, Anda akan mencatat bahwa fungsi yang dipanggil ( PerformZebraAction) itu sendiri bertanggung jawab untuk mengirimkan data (dalam hal ini Zebracontoh ) ke fungsi - data tidak berasal dari kode panggilan.
Karena pendekatan terbalik menggunakan fungsi urutan yang lebih tinggi dengan cara ini, pada saat Actiondipanggil, itu adalah turunan yang lebih banyak Zebrayang dipanggil terhadap zebraActionfungsi (dilewatkan sebagai parameter), meskipun fungsi itu sendiri menggunakan tipe yang kurang diturunkan.