Как лучше всего наследовать массив, который должен хранить данные, специфичные для подкласса?

Я пытаюсь настроить иерархию наследования, подобную следующей:

abstract class Vehicle
{
  public string Name;
  public List<Axle> Axles;
}

class Motorcycle : Vehicle
{
}

class Car : Vehicle
{
}

abstract class Axle
{
  public int Length;
  public void Turn(int numTurns) { ... }
}

class MotorcycleAxle : Axle
{
  public bool WheelAttached;
}

class CarAxle : Axle
{
  public bool LeftWheelAttached;
  public bool RightWheelAttached;
}

Я хотел бы хранить только объекты MotorcycleAxle в массиве Axles объекта Motorcycle и объекты CarAxle в массиве Axles объекта Car. Проблема в том, что нет способа переопределить массив в подклассе, чтобы заставить один или другой. В идеале для класса Motorcycle было бы допустимо что-то вроде следующего:

class Motorcycle : Vehicle
{
  public override List<MotorcycleAxle> Axles;
}

но типы должны совпадать при переопределении. Как я могу поддерживать эту архитектуру? Придется ли мне просто выполнять много проверок типов и приведения типов во время выполнения везде, где есть доступ к элементу Axles? Мне не нравится добавлять проверки типов во время выполнения, потому что вы начинаете терять преимущества строгой типизации и полиморфизма. В этом сценарии должны быть хотя бы некоторые проверки во время выполнения, поскольку свойства WheelAttached и Left / RightWheelAttached зависят от типа, но я хотел бы минимизировать их.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
4 669
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Используйте больше дженериков

abstract class Vehicle<T> where T : Axle
{
  public string Name;
  public List<T> Axles;
}

class Motorcycle : Vehicle<MotorcycleAxle>
{
}

class Car : Vehicle<CarAxle>
{
}

abstract class Axle
{
  public int Length;
  public void Turn(int numTurns) { ... }
}

class MotorcycleAxle : Axle
{
  public bool WheelAttached;
}

class CarAxle : Axle
{
  public bool LeftWheelAttached;
  public bool RightWheelAttached;
}

На ум приходят 2 варианта. 1 использует дженерики:

abstract class Vehicle<TAxle> where TAxle : Axle {
   public List<TAxle> Axles;
}

Второй использует затенение - и это предполагает, что у вас есть свойства:

abstract class Vehicle {
   public IList<Axle> Axles { get; set; }
}

class Motorcyle : Vehicle {
   public new IList<MotorcycleAxle> Axles { get; set; }
}

class Car : Vehicle {
   public new IList<CarAxle> Axles { get; set; }
}

void Main() {
   Vehicle v = new Car();
   // v.Axles is IList<Axle>

   Car c = (Car) v;
   // c.Axles is IList<CarAxle>
   // ((Vehicle)c).Axles is IList<Axle>

Проблема с затенением в том, что у вас есть общий список. К сожалению, вы не можете ограничить список только CarAxle. Кроме того, вы не можете использовать Listв List- хотя там есть цепочка наследования. Вы должны преобразовать каждый объект в новый список (хотя с LINQ это становится намного проще).

Я бы сам пошел за дженериками.

Я спросил аналогичный вопрос и получил лучший ответ, проблема связана с поддержкой C# ковариации и контравариантности. См. Это обсуждение для получения дополнительной информации.

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