Сначала определимся с терминологией:
Имеется три возможных варианта поведения:
Ковариантность: позволяет использовать более конкретный тип, чем заданный изначально
Контравариантность: позволяет использовать более универсальный тип, чем заданный изначально
Инвариантность: позволяет использовать только заданный тип
Все типы данных в с# унаследованы от базового типа Object, что позволяет нам сделать вот такие штуки:
object animal = "cat"; object i = 1; object o = new string[5]; Это называется проявления ковариантности, когда можно присвоить более конкретный тип в тип стоящий выше по ерархии.
Посмотрим следующие примеры: List animal = new List() //<- Компилятор не даст скомпилировать. object[] animal = new string[5]; //<- а так даст. Массивы ссылочных типов поддерживают ковариантность object[] animalInt = new int[5]; //<- а так нет, массивы значимых типов инвариантны