Ковариантность и контравариантность в C#

Ковариантность и контравариантность в 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  


Report Page