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

Ковариантность и контравариантность — это механизмы, позволяющие гибко работать с обобщенными интерфейсами и делегатами в C#. Они определяют, как можно передавать и возвращать объекты разных типов в контексте наследования.

Ковариантность

Ковариантность (out) позволяет использовать производный тип вместо базового при возврате значений из метода.

Пример ковариантности с интерфейсом:

interface ICovariant<out T>
{
    T GetItem();
}

class Base {}
class Derived : Base {}

class CovariantExample : ICovariant<Derived>
{
    public Derived GetItem() => new Derived();
}

class Program
{
    static void Main()
    {
        ICovariant<Base> baseItem = new CovariantExample();
        Console.WriteLine(baseItem.GetItem().GetType());
    }
}

Ковариантность применяется только к выходным (out) параметрам, так как передача значений обратно вызывает проблемы с безопасностью типов.

Контравариантность

Контравариантность (in) позволяет использовать базовый тип вместо производного при передаче параметров в метод.

Пример контравариантности с интерфейсом:

interface IContravariant<in T>
{
    void SetItem(T item);
}

class Base {}
class Derived : Base {}

class ContravariantExample : IContravariant<Base>
{
    public void SetItem(Base item) => Console.WriteLine(item.GetType());
}

class Program
{
    static void Main()
    {
        IContravariant<Derived> derivedItem = new ContravariantExample();
        derivedItem.SetItem(new Derived());
    }
}

Контравариантность применяется только к входным (in) параметрам, так как использование производного типа вместо базового может привести к ошибкам типов.

Ковариантность и контравариантность в делегатах

Делегаты также поддерживают ковариантность и контравариантность:

delegate Base CovariantDelegate();
delegate void ContravariantDelegate(Derived d);

class Program
{
    static Derived GetDerived() => new Derived();
    static void ProcessBase(Base b) => Console.WriteLine(b.GetType());
    
    static void Main()
    {
        CovariantDelegate covariant = GetDerived;
        ContravariantDelegate contravariant = ProcessBase;
        
        Console.WriteLine(covariant().GetType());
        contravariant(new Derived());
    }
}

Ковариантность и контравариантность позволяют использовать более гибкие структуры в обобщенных интерфейсах и делегатах, обеспечивая безопасность типов и удобство работы с наследуемыми классами.