Reputation: 7449
Let's start with the following taxonomy
public abstract class Automobile { }
public class Automobile<T> : Automobile where T : Automobile { }
public class Car : Automobile<Car> { }
public class Truck : Automobile<Truck> { }
public class SmartAutomobile<T> : Automobile<T>
{
public T MyAutomobile { get; set; }
public SmartAutomobile(SmartTechnology smart)
{
// Cannot implicitly convert Automobile to T
this.MyAutomobile = AutomobileFromSmart(typeof(T), smart);
}
public static Automobile AutomobileFromSmart(Type type, SmartTechnology smart)
{
if (type == typeof(Car))
return new Car { /* ... */ };
else
throw new NotImplementedException("Car type " + type.FullName + " not recognized");
}
}
public class SmartTechnology { }
As you can see from the comment, the compiler says it cannot convert an Automobile
to T
in SmartAutomobile<T>
's constructor. How can this be? the compiler should know that T
, because of the constraint in Automobile<T>
, is an Automobile
.
If I try to explicitly cast it
this.MyAutomobile = AutomobileFromSmart(typeof(T), smart) as T;
I get the compiler error
The type parameter 'T' cannot be used with the 'as' operator because it does not have a class type constraint nor a 'class' constraint
Now if I also define the where
constraint in SmartAutomobile<T>
public class SmartAutomobile<T> : Automobile<T> where T : Automobile
The compiler doesn't show any error
But if I remove the explicit cast:
this.MyAutomobile = AutomobileFromSmart(typeof(T), smart);
The Cannot implicitly convert Automobile to T error shows up again.
How can it be that the compiler doesn't realize the where
constraint forces T
to be an Automobile
?
Upvotes: 1
Views: 86
Reputation: 152521
How can it be that the interpreter doesn't realize the where constraint forces T to be an Automobile?
No, it forces T
to be derived from Automobile
. And since down-casting is not always safe, you can't implicitly cast from Automobile
to T
. If T
was Car
, but AutomobileFromSmart
returned a Truck
, then the cast would fail at runtime. You can explicitly cast (or use as
) which tells the compiler "I know what I'm doing, and this cast will be safe at run-time").
Upvotes: 5
Reputation: 63732
Variance.
Your constraint says that T
is derived from Automobile
. That doesn't mean that T
is an Automobile
- it could also be a class derived from Automobile
. And now you have a situation where a variable that should always be of type MyAutomobile
is of type Automobile
instead - that's a complete violation of the type system.
So you can pass T
anywhere you can pass an Automobile
, but it doesn't apply vice versa - just like you can pass Label
instead of Control
, but not vice versa.
Your second problem is that generic constraints aren't inherited. You do have to mention them everywhere they are required. That's why SmartAutomobile<T>
still needs the same constraint that's also in Automobile<T>
.
Upvotes: 4