Ковариация массива нарушена? Да!

Примеры кода, которые я показываю здесь, относятся к C#, а также к Java, потому что я думаю, что Java была первой, кто сделал (сломал?) это. C# вынужден был делать то же самое на своем языке.

Дисперсия (ковариантность, контравариантность, инвариантность) — сложная для объяснения тема. Но я все равно попробую.

При разработке языка с типами разработчики должны установить определенные правила о том, как типы и связанные с ними типы заменяются. Любая операция, связанная с типами, подчиняется этим правилам. Операция может быть ковариантной, контравариантной или инвариантной. Обратите внимание, что операция может быть не только функцией/методом, но и присваиванием.

Рассмотрим эти классы. Small и Little являются подклассами Big.

public class Big
{
}
public class Small : Big
{
}
public class Little : Big
{
}

Если я могу присвоить тип Small типу Big, то эта операция присваивания была ковариантна с этими типами.

Big a = new Small();
Big b = new Little();

Обратное неверно для описанной выше операции. Контравиантность является обратной ковариантностью. Ковариантность кажется более интуитивной, чем контравариантность. Операция присвоения не противоречит этим типам. Если вы попытаетесь это сделать, компилятор должен вас предупредить, потому что правила дисперсии четко определены.

Small s = new Big();

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

В этом посте я буду говорить только о ковариации разбитого массива.

Разработчики языка решили сделать массивы ковариантными. Следующий код прекрасно компилируется.

Big[] bigArray = new Small[10];

Вышеупомянутая операция присвоения является законной, поскольку она подчиняется правилам дисперсии.

Теперь рассмотрим следующий код. Мы используем bigArray, определенный в предыдущем коде. Этот код также совершенно легальный. Поскольку объект Small можно присвоить типу Big. Он удовлетворяет правилам ковариации.

bigArray[0] = new Small();

Little, присвоенное Big, также следует правилам ковариации.

bigArray[1] = new Little();

Вот где что-то идет не так. Приведенный выше код по-прежнему является вполне допустимым кодом. Плохой компилятор не знает, что среда выполнения не согласна.

Если вы запустите программу, она выдаст исключение, пытающееся присвоить Little Big. Вот почему ковариация массива нарушена. Проблема в том, что хотя тип Big, базовое хранилище имеет тип Small. Small и Little не заменяются друг другом.

Могли ли разработчики языка избежать этой проблемы? Может быть. Давайте создадим List вместо массива.

List<Big> bigs = new List<Small>();

Приведенный выше код не компилируется. Здесь компилятор полностью избегает этой проблемы, чтобы обеспечить безопасность типов.

Если вы работаете с массивом со связанными типами, перед вставкой объекта в массив сначала проверьте его тип, чтобы избежать ошибок во время выполнения. В следующий раз, когда вы будете просматривать код с массивами, помните, что ковариация массивов нарушена!

Надеюсь, вам было интересно. Спасибо за внимание.

Привет 🍺