You cannot use a derived class of the second level as a parameter of a type of a base class

I have an interesting situation where some things work and others do not, and I'm not sure why. Below is the code that brings my situation closer. I have static in the repository, which takes a generic type, which is implemented by the base type of the object. Then I have two levels of derived classes based on this generic type. The first level of the derived class fills the general parameters of the base type and works fine, however, any class that derives from the class that fills the general parameters does not work instead of the base class from which it is derived.

public class Vehicle<TVehicleType, TStorage> { } public class Car : Vehicle<Car, ParkingLot> { } public class PickupTruck : Car { } public class Dealership <TDrivableVehicle> { public static TDrivableVehicle GetVehicle<TVehicleType, TStorage>(TStorage lot) where TDrivableVehicle : Vehicle<TVehicleType, TStorage>, new() { } } public class CarDealership : Dealership<Car> { public static Car GetDrivableVehicle(aCarParkingLot) { return Dealership.GetDrivableVehicle<Car, CarParkingLot>(aCarParkingLot); <-- Works fine } } public class PickupTruckDealership : CarDealership { public static PickupTruck GetDrivableVehicle(aCarParkingLot) { return Dealership.GetDrivableVehicle<PickupTruck, CarParkingLot>(aCarParkingLot); <-- fails } } 

Some aspects seem to work correctly from the point of view of PickupTruck, who understands its common base, but extensions (not shown here) and passing the type to the type parameter do not specifically (calling GetDrivableVehicle). I assume that the extension method is related to the type parameter problem, since it will have to determine the type.

Any ideas why this doesn't work and / or what can be done to get around it?

+4
source share
3 answers

Rewriting your code to such an extent that I can make it fail when you say it will be - the problem is exactly what Tom Smith says: PickupTruck inherits Car and therefore is Vehicle<Car, ParkingLot> and not Vehicle<PickupTruck, ParkingLot> . In addition, because of this common inheritance, it is impossible for it to be anything other than this.

I know that your code is only a concise representation of the problem you are facing, but if it is close enough, there may be some useful points that we can make here about the general architecture.

I am not opposed to making common bases aware of my colleagues - indeed, this is especially useful for factories; however, it almost always instantly excludes further inheritance.

You are trying to encode too much information at the type level; and besides the number of angle brackets that we see here, it actually hints at a slightly inappropriate character that Vehicle<TVehicleType, TStorage> determines the type of storage that it can store inside.

It just doesn’t make any sense to me, because they say that we have ParkingLot today, but tomorrow we also get Hangar (for cars that are stored undercover) - this will require a completely new swathe of types of vehicles that are unequal in due to the fact that we also have a derived type passed to the Vehicle<TDerived, ...> ParkingLotCar - ergo ParkingLotCar , and HangarCar never be equivalent, even if two instances represent the same make / model, etc.

So, expecting this, you went on an inheritance, where you have a common Car , but, of course, at this point, any inheritance is meaningless, because Car is Vehicle<Car,...> , so all that should be from it should be. Only with multiple inheritance, perhaps this is not so, but even with this he did not go around the whole ParkingLot issue.

Ask yourself why Vehicle<,> needs to know about the type of receipt? Does this mean that you can have one factory method? In this case, you must put it in the Dealership or ParkingLot ; Not Vehicle Base:

 public interface IVehicle {} public interface ICar : IVehicle {} //because pickup trucks share some car traits public interface IPickup : ICar, IVehicle {} public interface IStorage {} public class Car : ICar, IVehicle {} public class Pickup : IPickup, ICar, IVehicle {} public class ParkingLot : IStorage {} public class Hangar : IStorage {} public class Dealership { public static TVehicle GetVehicle<TVehicle>(IStorage storage) where TVehicle : IVehicle, new() { } } //now you can specialise if you really need to public class CarDealership { public static Car GetVehicle(IStorage storage) { return Dealership.GetVehicle<Car>(storage); } } public class PickupDealership { public static Pickup GetVehicle(IStorage storage) { return Dealership.GetVehicle<Pickup>(storage); } } 

You now have a time relationship between vehicle types; allowing various specific types to share features, such as ICar or IPickup ; but you have broken the connection between the car and the storage so you can get IVehicle from FootballPitch or disconnect it from the pier to RiverBed if you need to.

+2
source

As you set it up, PickupTruck not Vehicle<PickupTruck, CarParkingLot> , but just a Vehicle<Car, CarParkingLot> . This type of infinite template recursion can be confusing. You can solve this problem by declaring PickupTruck : Vehicle<PickupTruck, CarParkingLot> , dropping the variance or refactoring the class hierarchy with co [ntra] to avoid a confusing template.

+1
source

You are trying to use a derived class instead of your base class. This will not work. You should be able to use your BASE class instead of a more derived class, and not vice versa. You can convert Car to an interface (ICar) and use PickupTruck ICar. It will work.

Read contravariance versus covariance. http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

0
source

All Articles