Marco Scabbiolo
Marco Scabbiolo

Reputation: 7449

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 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

Answers (2)

D Stanley
D Stanley

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

Luaan
Luaan

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

Related Questions