Каков наилучший способ наследовать массив, в котором необходимо хранить данные, относящиеся к подклассу?
-
09-06-2019 - |
Вопрос
Я пытаюсь настроить иерархию наследования, аналогичную следующей:
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.Проблема в том, что нет способа переопределить массив в подклассе, чтобы принудительно использовать тот или иной параметр.В идеале для класса мотоциклов было бы справедливо что-то вроде следующего:
class Motorcycle : Vehicle
{
public override List<MotorcycleAxle> Axles;
}
но типы должны совпадать при переопределении.Как я могу поддерживать эту архитектуру?Придется ли мне просто выполнять большую проверку типов во время выполнения и приведение везде, где осуществляется доступ к элементу Axles?Мне не нравится добавлять проверки типов во время выполнения, потому что вы начинаете терять преимущества строгой типизации и полиморфизма.В этом сценарии должны быть по крайней мере некоторые проверки во время выполнения, поскольку свойства WheelAttached и Left / RightWheelAttached зависят от типа, но я хотел бы свести их к минимуму.
Решение
Используйте больше дженериков
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.Кроме того, вы не можете преобразовать список<Axle> в список<CarAxle> - даже если там есть цепочка наследования.Вы должны привести каждый объект к новому списку (хотя с LINQ это становится намного проще).
Я бы сам выбрал дженерики.
Я спросил у похожий вопрос и получил лучший ответ: проблема связана с поддержкой C # ковариации и контравариантности.Смотрите это обсуждение для получения дополнительной информации.