Ковариантность и контравариантность в C#
C# Cooking
Ковариантность и контравариантность – это механизмы, которые позволяют использовать производные и базовые типы в обобщенных интерфейсах и делегатах.
🔹 Ковариантность (out) → позволяет использовать производный тип вместо базового.
🔹 Контравариантность (in) → позволяет использовать базовый тип вместо производного.
Разберемся на примерах!
Ковариантность (out) – работает с возвращаемыми значениями
📌 Можно использовать производный тип там, где ожидается базовый.
📌 Работает только в out (то есть только на вывод, возвращаемый тип).
Пример:
Допустим, у нас есть класс Fruit (фрукт) и Apple (яблоко, который является фруктом):
class Fruit { }
class Apple : Fruit { }
Теперь создадим интерфейс производителя (Producer), который умеет "производить" (Produce) объекты типа T:
interface IProducer<out T>
{
T Produce();
}
Обратите внимание: out T – это ковариантность.
Теперь реализуем IProducer для Apple:
class AppleProducer : IProducer<Apple>
{
public Apple Produce() => new Apple();
}
И самое интересное:
IProducer<Fruit> fruitProducer = new AppleProducer(); // Это работает!
📌 Почему это работает?
Потому что IProducer<out T> ковариантный, и IProducer<Apple> можно использовать как IProducer<Fruit>, ведь Apple – это подтип Fruit.
Контравариантность (in) – работает с входными параметрами
📌 Можно использовать базовый тип там, где ожидается производный
📌 Работает только в in (только на вход, параметры метода)
Пример:
Допустим, у нас есть интерфейс потребителя (Consumer), который принимает объекты типа T:
interface IConsumer<in T>
{
void Consume(T item);
}
Здесь in T – это контравариантность.
Теперь создадим класс, который принимает любой Fruit:
class FruitConsumer : IConsumer<Fruit>
{
public void Consume(Fruit item)
{
Console.WriteLine("Съеден фрукт!");
}
}
📌 А теперь самое интересное:
IConsumer<Apple> appleConsumer = new FruitConsumer(); // Это работает!
📌 Почему это работает?
Потому что IConsumer<in T> контравариантный, и IConsumer<Fruit> можно использовать как IConsumer<Apple>, ведь Apple – это Fruit, а потребитель фрукта (FruitConsumer) может обработать любое яблоко.
Важные ограничения
Особенность
➡ out (ковариантность) – для вывода данных
➡ in (контравариантность) – для ввода данных
Где используется?
✔ out → В возвращаемых значениях
✔ in → Во входных параметрах
Что разрешает?
✔ out → Использовать производные типы вместо базового
✔ in → Использовать базовые типы вместо производных
Можно ли использовать как параметр метода?
❌ out → Нет (только return)
✅ in → Да
Можно ли использовать как return?
✅ out → Да
❌ in → Нет
Итог
📌 Ковариантность (out) – используется для возвращаемых значений (работает с производными типами).
📌 Контравариантность (in) – используется для входных параметров (работает с базовыми типами).
📌 Простая аналогия:
- Ковариантность (
out) – как магазин, продающий Fruit, но реально продающий Apple 🍏 - Контравариантность (
in) – как повар, которому можно дать Fruit, и он приготовит Apple или Banana 👨🍳
Когда использовать?
- Если интерфейс возвращает
T, но не принимает его в параметры метода → используемout(ковариантность) - Если интерфейс принимает
Tкак параметр, но не возвращает его → используемin(контравариантность)
Таким образом, ковариантность и контравариантность помогают делать обобщенные интерфейсы более гибкими, расширяя возможности программирования в C#. 🚀
#CSharp #Программирование #Generics #Ковариантность #Контравариантность #Разработка #IT