Search code examples
c#classinheritancepolymorphism

C# class inheritance and polymorphism with polymorphic fields


I'm running into an occasional design issue with classes, mostly in some video game stuff. The problem arises when a class that extends another class (such as a Player) has a field that is also a class that has its own extended classes. I know this is a pattern issue but not sure how to resolve it properly.

Let's take a super simple example.

  1. A class called Engine that drives
  2. A class called SpecialEngine that charges
  3. A class called Car with an Engine
  4. A class called SpecialCar with a SpecialEngine
public class Engine {
  public void Drive() {}
}

public class SpecialEngine : Engine {
  public void Charge() {}
}

public class Car {
  protected Engine engine;
  public virtual void Move() {
    this.engine.Drive();
  }
}

public class SpecialCar : Car {
  // Both SpecialCar.engine and SpecialCar.specialEngine 
  // would need to be set to an instance of SpecialEngine
  private SpecialEngine specialEngine;
  public override void Move() {
    // or.. cast this.engine to SpecialEngine? 
    this.specialEngine.Charge();
    base.Move();
  }
}

Car has an Engine, and you can pass a SpecialEngine to Car (idk if C# uses the term "slicing" but this at least works in C++). But you cannot access any properties of SpecialEngine. So you make SpecialCar that could accept a SpecialEngine as its engine but you want to access SpecialEngine, so you make a second field called specialEngine and now have two references to it?

My thinking here is wrong and I'm not sure what I should do. I know I could have maybe a generic Car<T> but I feel like I may end up with Car<A, B, C, D...>.


Solution

  • C# supports Covariant returns. Just can use a single SpecialEngine in SpecialCar.

    public class Engine
    {
        public void Drive() { }
    }
    
    public class SpecialEngine : Engine
    {
        public void Charge() { }
    }
    
    public class Car
    {
        protected virtual Engine Engine { get; }
        public virtual void Move()
        {
            Engine.Drive();
        }
    }
    
    public class SpecialCar : Car
    {
        protected override SpecialEngine Engine { get; }
        public override void Move()
        {
            Engine.Charge();
            base.Move();
        }
    }