views:

439

answers:

8

I'm trying to set up an inheritance hierarchy similar to the following:

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;
}

I would like to only store MotorcycleAxle objects in a Motorcycle object's Axles array, and CarAxle objects in a Car object's Axles array. The problem is there is no way to override the array in the subclass to force one or the other. Ideally something like the following would be valid for the Motorcycle class:

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

but the types have to match when overriding. How can I support this architecture? Will I just have to do a lot of run-time type checking and casting wherever the Axles member is accessed? I don't like adding run-time type checks because you start to lose the benefits of strong typing and polymorphism. There have to be at least some run-time checks in this scenario since the WheelAttached and Left/RightWheelAttached properties depend on the type, but I would like to minimize them.

+5  A: 

Use more generics

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;
}
Lars Truijens
A: 

Lars' example looks intriguing. Accepting that answer for now while I investigate further.

Luke
A: 

I'm a C++ guy who only knows a little about C#, but I assume most of the concepts are the same.

An alternative approach would be to move the declaration of the axle list out of the base class and into the derived classes. You can then add pure virtual functions to the base class to access the axles, which the derived classes would then implement.

The problem I see with

abstract class Vehicle<T>

Is that 'T' is not truly generic in this case - you are expecting it to specifically be an axle derived class.

17 of 26
A: 

17 of 26: What would the type of the pure virtual functions be in that case? At this point I'm not too worried about losing true generic behavior.

Luke
A: 

In C++, you could do any of the following:

virtual List<Axle>  GetAxles();   // Copy of list
virtual List<Axle>* GetAxles();  // Pointer to list
virtual List<Axle>& GetAxles();  // Reference to list

Or you could create functions to iterate over the list.

17 of 26
A: 

17 of 26: But then in the Motorcycle/Car classes I would have to use the same type (List<Axle>) right?

Luke
A: 

2 options spring to mind. 1 is using generics:

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

The second uses shadowing - and this assumes you have properties:

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>

The problem with shadowing is that you have a generic List. Unfortunately, you can't constrain the list to only contain CarAxle. Also, you can't cast a List<Axle> into List<CarAxle> - even though there's an inheritance chain there. You have to cast each object into a new List (though that becomes much easier with LINQ).

I'd go for generics myself.

Mark Brackett
A: 

I asked a similar question and got a better answer, the problem is related to C#'s support for covariance and contravariance. See that discussion for a little more information.

Luke