Reputation: 491
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
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:
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
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
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