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, мы часто имеем интерфейсы. Поскольку интерфейсы позволяют нам иметь типовые отношения между этими материализациями универсальных типов, которые мы можем затем использовать для назначения одного другому. Честно говоря, ваши абстрактные классы с этими защищенными полями затрудняют очистку с помощью интерфейсов, поэтому вам, вероятно, следует подумать о том, нужны ли вам на самом деле такого рода отношения типов и есть ли абстрактный базовый тип для этих полей. на самом деле необходимо.