Home > Enterprise >  Casting to a generic type based on the concrete type of a parameter when only give its supertype
Casting to a generic type based on the concrete type of a parameter when only give its supertype

Time:01-05

I've been trying to write a method that given a parameter, it will return a new instance of a generic class whose generic type is based on the type of that parameter. Well, it'd be easier to illustrate with code.

First we have:

// Vehicles
abstract class Vehicle { }
class Car : Vehicle { }
class Airplane : Vehicle { }

// Containers for vehicles
abstract class VehicleContainerBase { }
abstract class VehicleContainer<T> : VehicleContainerBase where T : Vehicle { }

// One concrete container for each vehicle type
class CarContainer : VehicleContainer<Car> { }
class PlaneContainer : VehicleContainer<Airplane> { }

Now, I would like a method where you receive the supertype Vehicle - we don't know whether it's a Car or Airplane - but if it happens to be a Car it will return an object of apparent type VehicleContainer<Car>.

I know that I can simply return VehicleContainerBase and call it a day, but I wonder if it is at all possible to do it as I originally wanted. Here's the reproducible full code of my attempt:

(https://dotnetfiddle.net/TemdGk)

using System;
using System.Collections.Generic;
                
public class Program
{
    // Delegate representing the construction of a container
    delegate VehicleContainerBase CreateNewContainer();
    
    // Mapping types to their corresponding construction delegates
    private static Dictionary<Type, CreateNewContainer> mapping = new Dictionary<Type, CreateNewContainer>();

    public static void Main(string[] args)
    {
        // "Register" the delegates so that they can be invoked as needed
        mapping.Add(typeof(Airplane), () => new PlaneContainer());
        mapping.Add(typeof(Car), () => new CarContainer());
        
        // Test it out
        var someCar = new Car(); // pretend we don't know this e.g. some API just returns Vehicle
        var carAsVehicle = (Vehicle)someCar;
        var theContainer = CreateContainer(carAsVehicle);
        Console.WriteLine(theContainer.GetType());
    }
    
    // This is the method that I'd like to build
    // I want the return type to be concretely VehicleContainer of the given parameter's type, instead of just VehicleContainerBase
    static VehicleContainer<VEHICLE_TYPE> CreateContainer<VEHICLE_TYPE>(VEHICLE_TYPE vehicle) where VEHICLE_TYPE : Vehicle
    {
        var createdContainer = mapping[vehicle.GetType()].Invoke();
        var castedContainer = (VehicleContainer<VEHICLE_TYPE>)createdContainer;  // <-- cannot cast
        return castedContainer;
    }
}

// Vehicles
public abstract class Vehicle { }
public class Car : Vehicle { }
public class Airplane : Vehicle { }

// Containers for vehicles
public abstract class VehicleContainerBase { }
public abstract class VehicleContainer<T> : VehicleContainerBase where T : Vehicle { }

// One concrete container for each vehicle type
public class CarContainer : VehicleContainer<Car> { }
public class PlaneContainer : VehicleContainer<Airplane> { }

Code fails at run-time with InvalidCastException:

Unable to cast object of type 'CarContainer' to type 'VehicleContainer`1[Vehicle]'.

I thought that I could cast it to (VehicleContainer<VEHICLE_TYPE>) which would be translated to (VehicleContainer<Car>), but that would not work.

Is there a way to implement this method so that the return type is how I hoped for originally in this example, or is my only option to change the signature and return VehicleContainerBase instead?

Thinking calmly, I don't think it's possible since the compiler can't guarantee that it can be cast... But I'm new to generics so maybe there is another way?

CodePudding user response:

Since you don't know the resulting type at compile time you can't really define variable of that type... So your only option is to use reflection (or dynamic which will implement correct reflection code for you):

dynamic theContainer = CreateContainer((dynamic)carAsVehicle);

Your attempt fails because type you ask to return does not match actual type of container that was created/found. If you want to return result of some base type instead of exact type of the container VehicleContainerBase is indeed the safe option. Alternatively returning null on mismatch case could do. Finally, you may want to read on variance and possibly get IVehicleContainer<Vehicle> to work.

  • Related