Marco Scabbiolo Marco Scabbiolo - 9 days ago 8
C# Question

Why is it needed to cast a generic type when a where constraint should be enough

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 interpreter says it cannot convert an
Automobile
to
T
in
SmartAutomobile<T>
's constructor. How can this be? the interpreter 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 interpreter 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 interpreter 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 interpreter doesn't realize the
where
constraint forces
T
to be an
Automobile
?

Answer

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").