Ось простий приклад використання ієрархії спадкування.
З огляду на просту ієрархію класів:

І в коді:
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
Інваріантність (тобто параметри загального типу * не * прикрашені inабо outключові слова)
Здається, такий метод, як цей
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
... повинен прийняти неоднорідну колекцію: (що вона робить)
var myAnimals = new List<LifeForm>
{
new Giraffe(),
new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra
Однак передача колекції більш похідного типу не вдається!
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>'
Чому? Оскільки загальний параметр IList<LifeForm>не є коваріантним -
IList<T>є інваріантним, тому IList<LifeForm>приймає лише колекції (які реалізують IList) там, де Tповинен бути параметризований тип LifeForm.
Якщо реалізація методу PrintLifeFormsбула шкідливою (але має такий же підпис методу), причина, через яку компілятор перешкоджає передачі, List<Giraffe>стає очевидною:
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
lifeForms.Add(new Zebra());
}
Оскільки IListдозволяє додавати або видаляти елементи, будь-який підклас з LifeFormцього приводу може бути доданий до параметра lifeFormsі порушує тип будь-якої колекції похідних типів, переданих методу. (Тут шкідливий метод намагатиметься додати Zebraдо var myGiraffes). На щастя, компілятор захищає нас від цієї небезпеки.
Коваріація (загальна з параметризованим типом, прикрашена out)
Коваріація широко використовується для незмінних колекцій (тобто там, де нові елементи не можуть бути додані або вилучені з колекції)
Рішенням прикладу вище є забезпечення коваріантного загального типу колекції, наприклад IEnumerable(визначеного як IEnumerable<out T>). IEnumerableне має методів зміни до колекції, і в результаті outковаріації тепер будь-яка колекція з підтипом LifeFormможе бути передана методу:
public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
PrintLifeFormsтепер може бути викликаний Zebras, Giraffesі будь-який IEnumerable<>з будь-якого підкласуLifeForm
Протиріччя (загальне із типом, параметризованим типом, прикрашене in)
Протилежність часто використовується, коли функції передаються як параметри.
Ось приклад функції, яка приймає Action<Zebra>як параметр і викликає його у відомій екземплярі Zebra:
public void PerformZebraAction(Action<Zebra> zebraAction)
{
var zebra = new Zebra();
zebraAction(zebra);
}
Як і очікувалося, це працює чудово:
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra
Інтуїтивно це не вдасться:
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction);
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
Однак це вдається
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal
і навіть це також вдається:
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba
Чому? Тому що Actionвизначається як Action<in T>, тобто це contravariant, означає, що для Action<Zebra> myAction, що myActionможе бути якнайбільше "a" Action<Zebra>, але менш похідні суперкласи Zebraтакож є прийнятними.
Хоча це може бути спочатку не інтуїтивно зрозумілим (наприклад, як можна Action<object>передавати його як параметр, що вимагає Action<Zebra>?), Якщо ви розпакуєте етапи, ви зауважите, що сама викликана функція ( PerformZebraAction) відповідає за передачу даних (у цьому випадку Zebraекземпляр ) до функції - дані не надходять з коду виклику.
Через інвертований підхід використання функцій вищого порядку таким чином, до моменту Actionвиклику це більш похідний Zebraекземпляр викликається zebraActionфункцією (передається як параметр), хоча сама функція використовує менш похідний тип.