dahvyd
dahvyd

Reputation: 491

Can't cast derived type to base abstract class with type parameter

I have a simple factory method which provides a concrete implementation instance based on a generic type parameter provided. If the concrete classes inherit from a common abstract base class with a type parameter I cannot cast them. The compiler tells me Error 2 Cannot convert type 'Car' to 'VehicleBase<T>'. It works fine if I substitute the abstract class for an interface with the same type parameter, or if I remove the generic type parameter from the abstract class.

interface IWheel
{
}

class CarWheel : IWheel
{
}

abstract class VehicleBase<T>
{
}

class Car : VehicleBase<CarWheel>
{
}

class VehicleFactory
{
    public static VehicleBase<T> GetNew<T>()
    {
        if (typeof(T) == typeof(CarWheel))
        {
            return (VehicleBase<T>)new Car();
        }
        else
        {
            throw new NotSupportedException();
        }
    }
}

This fails to compile on (VehicleBase<T>)new Car(). Is this a compiler defect, or could this be a deliberate design decision to treat abstract classes and interfaces with type parameters differently?

As a workaround I can always make the abstract class implement an interface and use this as the return value for my factory method, but I'd still like to know why this behavior is happening.

Upvotes: 13

Views: 10793

Answers (3)

Botz3000
Botz3000

Reputation: 39600

This seems to work:

return new Car() as VehicleBase<T>;

My guess why it is that way:
As generic type instances of VehicleBase<T> are not related, it cannot be proven that casting them could work:

enter image description here

If T is of type Blah, the cast would not work. You can't go back to object and then take the other branch (there's no multiple inheritance in C# after all).

By casting it back to object before, you are again opening the possibility that the cast might work, because there might still be a path down to VehicleBase<CarWheel>. An interface, of course, can appear anywhere in this tree below object, so that should work, too.

Upvotes: 3

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131189

This is neither a compiler defect nor a deliberate decision. Type parameters on generic classes are neither covariant nor contravariant, ie there is no inheritance relationship between specializations of the same generic class. From the docs:

In the .NET Framework version 4, variant type parameters are restricted to generic interface and generic delegate types.

Which means that the following code will compile, because it uses an interface instead of an abstract class:

interface IWheel
{
}

class CarWheel : IWheel
{
}

interface IVehicleBase<T> 
{
}

class Car : IVehicleBase<CarWheel>
{
}

class VehicleFactory
{
    public static IVehicleBase<T> GetNew<T>() 
    {
        if (typeof(T) == typeof(CarWheel))
        {
            return (IVehicleBase<T>)new Car();
        }
        else
        {
            throw new NotSupportedException();
        }
    }
}

Check "Covariance and Contravariance in Generics" for more info and examples.

There is also a Covariance and Contravariance FAQ at the C# FAQ blog with more info, and an 11-part series! on the subject by Eric Lippert

Upvotes: 6

Marc Gravell
Marc Gravell

Reputation: 1062550

That is not provable, because generic code needs to work (with the same IL) for every possible T, and there is nothing to say that Car : VehicleBase<float>, for example. The compiler does not over-analyse the fact that the if check sows that T is CarWheel - the static checker treats each statement separately, it doesn't try to understand the cause-and-effect of conditions.

To force it, cast to object in the middle:

return (VehicleBase<T>)(object)new Car();

However! Your approach isn't really "generic" as such.

Upvotes: 11

Related Questions