C# универсальное наследование: инвариантность мешает вызову производного класса

Что я хочу сделать:

abstract class TileBase
{
    protected TileGroup<TileBase> tileGroup;
}
class Tile : TileBase
{
    public Tile(Province Province)
    {
        tileGroup = Province;
    }
}
abstract class TileGroup<T>
{
    protected T[] tiles;
    protected TileGroup<TileGroup<T>> tileGroup;
}
class Province : TileGroup<TileBase>
{

    public Province(Tile tile, Nation nation)
    {
        tiles = new[] { tile };
        tileGroup = nation;
    }
}
class Nation : TileGroup<Province>
{

    public Nation(Province province)
    {
        tiles = new[] { province };
        tileGroup = null;
    }
}

Это не будет работать из-за неизменности (если я правильно понимаю неизменность): cannot convert Nation to TileGroup<TileGroup<TileBase>>

Поэтому мне нужно написать это так:

class Nation : TileGroup<TileGroup<TileBase>>
{

    public Nation(Province province)
    {
        tiles = new[] { province };
        tileGroup = null;
    }
}

Но когда слои сложены; это становится ужасно быстро:

Map : TileGroup<TileGroup<TileGroup<TileBase>>> 

Это также затрудняет добавление слоев между двумя существующими слоями, потому что одно изменение в нижнем слое означает изменение всех верхних слоев.

Так как именно я должен делать это?


Извините за формулировку, я знаю, чего я хочу, но не то, как я должен объяснить это яснее, чем таким образом.

1 ответ

Решение

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

Когда ты пишешь TileGroup<TileBase> что на самом деле происходит, что новый тип, давайте назовем это TileGroup_TileBase определяется следующим образом:

class TileGroup_TileBase
{
    protected TileBase[] tiles;
    protected TileGroup<TileGroup<TileBase>> tileGroup;
}

Теперь давайте немного продолжим это расширение универсальных типов. Мы уже знаем TileGroup<TileBase> введите там, но есть также TileGroup<TileGroup_TileBase>Итак, давайте заменим это:

class TileGroup_TileBase
{
    protected TileBase[] tiles;
    protected TileGroup_TileGroup_TileBase tileGroup;
}
class TileGroup_TileGroup_TileBase
{
    protected TileGroup_TileBase[] tiles;
    protected TileGroup<TileGroup<TileGroup_TileBase>> tileGroup;
}

Мы могли бы продолжить здесь, но этого на самом деле достаточно, чтобы объяснить проблему. Давайте посмотрим на Nation вместо этого то, что вы пытаетесь назначить в Province конструктор. Nation это TileGroup<Province>Итак, давайте расширим это:

class TileGroup_Province
{
    protected Province[] tiles;
    protected TileGroup<TileGroup<Province>> tileGroup;
}

Итак, у нас есть достаточно расширенные типы. Давайте посмотрим на задание, которое вы пытаетесь выполнить. В Province, tileGroup свойство типа TileGroup<TileGroup<TileBase>>так что по сути это то, что вы пытаетесь сделать:

TileGroup<Province> nation = null;
TileGroup<TileGroup<TileBase>> province_tileGroup = nation;

Вы видите, почему это не удается сейчас? Если нет, давайте использовать вместо этого наши расширенные универсальные типы:

TileGroup_Province nation = null;
TileGroup_TileGroup_TileBase province_tileGroup = nation;

Хорошо, это фактические типы, которые используются (помните, что мы делаем это не просто для того, чтобы понять это, но что эти универсальные типы фактически реализуются для каждого T серьезно!). Но если мы посмотрим на определения выше, TileGroup_Province а также TileGroup_TileGroup_TileBase на самом деле не связаны. Конечно, они похожи, но нет типовых отношений, которые позволили бы такое назначение!

Вот почему на самом деле, когда мы имеем дело с универсальными типами в BCL, мы часто имеем интерфейсы. Поскольку интерфейсы позволяют нам иметь типовые отношения между этими материализациями универсальных типов, которые мы можем затем использовать для назначения одного другому. Честно говоря, ваши абстрактные классы с этими защищенными полями затрудняют очистку с помощью интерфейсов, поэтому вам, вероятно, следует подумать о том, нужны ли вам на самом деле такого рода отношения типов и есть ли абстрактный базовый тип для этих полей. на самом деле необходимо.

Другие вопросы по тегам