За допомогою <out T>
ви можете розглядати посилання на інтерфейс як одне вгору в ієрархії.
З <in T>
, ви можете розглядати посилання на інтерфейс як таке, що є посиланням донизу в пошуку.
Дозвольте мені спробувати пояснити це англійською мовою.
Скажімо, ви отримуєте список тварин із зоопарку і маєте намір їх обробити. Усі тварини (у вашому зоопарку) мають ім’я та унікальний посвідчення особи. Деякі тварини - ссавці, деякі - плазуни, деякі - земноводні, деякі - риби тощо, але всі вони тварини.
Отже, з вашим списком тварин (який містить тварин різних типів), ви можете сказати, що всі тварини мають ім’я, тому очевидно, що було б безпечно отримати ім’я всіх тварин.
Однак що, якщо у вас є лише список риб, але вам потрібно поводитися з ними як з тваринами, чи це працює? Інтуїтивно це має працювати, але в C # 3.0 і раніше цей фрагмент коду не компілюється:
IEnumerable<Animal> animals = GetFishes();
Причиною цього є те, що компілятор не знає, що ви маєте намір або що можете зробити з колекцією тварин після того, як ви її отримали. Незважаючи на те, що він знає, може бути спосіб IEnumerable<T>
повернути об’єкт назад до списку, і це потенційно дозволить вам помістити тварину, яка не є рибою, до колекції, яка повинна містити лише рибу.
Іншими словами, компілятор не може гарантувати, що це заборонено:
animals.Add(new Mammal("Zebra"));
Тож компілятор просто відмовляється від компіляції вашого коду. Це коваріація.
Давайте розглянемо противаріантність.
Оскільки наш зоопарк може обробляти всіх тварин, він, безсумнівно, може обробляти рибу, тому спробуємо додати трохи риби до нашого зоопарку.
У C # 3.0 і раніше це не компілює:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Тут компілятор може дозволити цей шматок коду, хоча метод повертається List<Animal>
просто тому, що всі риби є тваринами, тому, якщо ми просто змінили типи на це:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Тоді це буде працювати, але компілятор не може визначити, що ви не намагаєтесь зробити це:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
Оскільки список насправді є списком тварин, це заборонено.
Отже, протиріччя та спільне відхилення - це те, як ви поводитесь до посилань на об’єкти та що вам дозволено з ними робити.
in
І out
ключові слова в C # 4.0 конкретно позначає інтерфейс як один або інший. З in
, вам дозволено розміщувати загальний тип (зазвичай T) у вхідних -позиціях, що означає аргументи методу та властивості лише для запису.
З out
, вам дозволено розміщувати загальний тип у вихідних- позиціях, що є значеннями повернення методу, властивостями лише для читання та параметрами методу out.
Це дозволить вам робити те, що передбачалося робити з кодом:
IEnumerable<Animal> animals = GetFishes();
List<T>
має як вхідні, так і вихідні вказівки на Т, тому це не ко-варіант, не контра-варіант, а інтерфейс, який дозволив вам додавати об’єкти, наприклад:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
дозволить вам зробити це:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Ось декілька відео, які демонструють концепції:
Ось приклад:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Без цих позначок можна скласти наступне:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
або це:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants