Search code examples
c#.netoopgenericsinheritance

How to extend base class logic while being able to assign derived classes to it


I'm having a trouble understanding the following concept:

I'm trying to extend a base class with following properties:

public class MovingItem : Item
{
    public Vector2 Acceleration { get; set; }
    public Vector2 Velocity { get; set; }
    public Vector2 Position { get; set; }
}

and i have several Item classes describing different items e.g. Sphere, Weapon, Sprite etc., etc., they inherently have different logic to them, but they are all Items

in this case, because MovingItem and aforementioned Items are on the 'same level' in inheritance tree, i cannot assign a variable like this:

MovingItem mi = new Weapon() { ... };

Because obviously, Weapon is Item, so is MovingItem but the Weapon is not MovingItem

So obvious solution would be to make those Items to inherit MovingItem instead of Item but i could think about 2 problems with this kind of solution:

  1. What if can't change those classes, because they are third-party (like Controls in some UI library)
  2. What if, in the future, I'd like to extend Items logic in a inherently different way than MovingItem, like PickupableItem

I thought that something like that could work:

public class MovingItem<T> : T where T : Item
{
    public Vector2 Acceleration { get; set; }
    public Vector2 Velocity { get; set; }
    public Vector2 Position { get; set; }
}

I thought this could help me extend not the Item directly, but wrap those implementations, and not having to create possibly hundreds of classes by hand...

But unfortunately, it is not possible, compiler says that class can't inherit from type parameter:

CS0689: Cannot derive from 'T' because it is a type parameter

Ultimately i would like to achieve a solution where i could create a MovingItem object from other Item object, and then operate, like this MovingItem is an Item like so...

public class ItemCollection { ...
    public void Add(Item item) { ... }
}
...
MovingItem<Weapon> movingWeapon = new Weapon(); { ... }
itemsCollection.Add(movingWeapon); // .Add(...) accepts argument of type Item

So... is there a solution to this kind of problem, extend Item while still being an Item for all other classes that inherit it? How do i approach this?


Solution

  • In my experience, Composition is almost always preferable to Inheritance.

    public class Weapon : Item { }
    
    public class MovementState
    {
        public Vector2 Acceleration { get; set; }
        public Vector2 Velocity { get; set; }
        public Vector2 Position { get; set; }
    }
    
    public class MovingItem
    {
        public required Item Item {get;set;}
        public required MovementState MovementState {get;set;}
    }
    
        var movingItems = new List<MovingItem>();
        var movingWeapon = new MovingItem
        {
            Item = new Weapon
            {
                // ...
            },
            MovementState = new MovementState
            {
                // ...
            }
        };
        movingItems.Add(movingWeapon);
    

    The same pattern can be repeated for other purposes, like PickupableItem:

    public class PickupableItem
    {
        public required Item Item {get;set;}
        public Hero? HeldBy {get;set;}
    }
    

    This follows the Open/Closed Principle by allowing you to create new concepts like movement, holdability, etc., without having to change the original classes. This reduces coupling by making it so that code that just cares about a weapon's attributes doesn't also need to be exposed to details like whether it's being held or whether it's moving.