0

I'm trying to add a type-safe generically-typed container as a subtype of a non-generically-typed supertype.

It's for a compiler but for exposition, let me present it as as vehicle parts. You have a root class VehiclePart of which you can have normal subtypes (such as gearstick here) but apart can also contain sub-parts (such as PartContainer here), and I want to do this generically for compiler-checked type safety.

The code is below. It fails at newpc.Add(); and in return newpc;

My questions are

  • is this possible, and

  • is this even advisable?

There is something asymmetric about genericising a subtype that just smells bad. I've inherited these from a common superclass VehiclePart because it should make handling them more consistent (if it can be made to work anyway. I'm rather a noob at generics) but it doesn't feel right.

public abstract class VehiclePart {
    public abstract T cloneTypesafe<T>() 
                    where T : VehiclePart, new();
}


public class Gearstick : VehiclePart {

    public Gearstick() { }

    public override T cloneTypesafe<T>() {
        return new T();
    }
}


public class PartContainer<T> : VehiclePart 
                where T : PartContainer<T>, new() {
    private List<T> contents;

    public PartContainer() { 
        contents = new List<T>();
    }

    public virtual void Add<U>(U newItem) 
                            where U : T  { 
        contents.Add(newItem);
    }

    public override U cloneTypesafe<U>()
                        //  where U : T  it doesn't like this
        {
        PartContainer<T> newpc = new PartContainer<T>();
        foreach (var kid in contents) {
            newpc.Add<U>(kid.cloneTypesafe<U>());
        }
        return newpc;
    }


Edit: I was doing this wrong. The following code seems to do what I want, though I'm uncomfortable with it as I don't feel I understand it fully.

    public class PartContainer2<K> : VehiclePart
                where K : VehiclePart, new() {
        private List<K> contents;

        public PartContainer2() {
            contents = new List<K>();
        }

        public virtual void Add(K newItem) {
            contents.Add(newItem);
        }

        public PartContainer2<K> cloneTypesafe() {
            PartContainer2<K> newpc = new PartContainer2<K>();
            foreach (var kid in contents) {
                newpc.Add(kid.cloneTypesafe<K>());
            }
            return newpc;
        }

    }
user3779002
  • 566
  • 4
  • 17
  • 1
    Note that your `U` generic parameter does not do anything. `newItem` could just as well be a `T` in this example. I do not think there is necessarily anything smelly about a generic subclass to a non generic superclass, in some cases it is very useful to do it that way. – JonasH Jan 08 '21 at 13:37
  • @Charlieface So that's what the CRTP is! OK, thanks. As I've spent way too much time on this I will go a slightly different route and probably just generate reams of code to make a specialised but non-generic subclass for each that I want (GearstickCollection, WheelCollection etc). Dirty but it'll do until my grasp of generics improves. Thanks! – user3779002 Jan 08 '21 at 14:10
  • @JonasH ISWYM. I removed it and it gets rid of an error, more importantly I can see why it was a mistake originally. Now I'm left with just cloning the kid type-safely, but "kid.cloneTypesafe()" doesn't work (Edit because "T : PartContainer" where I want T to be a VehiclePart here) - any suggestions? – user3779002 Jan 08 '21 at 14:23
  • For cloning CRTP is an good alternative. Another option would be to use a visitor pattern. A visitor allows the cloning logic for all vehicleParts to be separated into a separate class that just deals with cloning. This has, like everything, advantages and disadvantages. – JonasH Jan 08 '21 at 15:25

0 Answers0