Why does a direct cast fail but the "as" operator succeed when testing a constrained generic type?

I used this question as the basis for a blog article in October 2015. Thanks for the great question!

Could someone please explain why this is the case?

"Why" questions are hard to answer; the answer is "because that's what the spec says" and then the natural question is "why does the spec say that?"

So let me make the question more crisp:

What language design factors influenced the decision to make the given cast operator illegal on constrained type parameters?

Consider the following scenario. You have a base type Fruit, derived types Apple and Banana, and, now comes the important part, a user-defined conversion from Apple to Banana.

What do you think this should do when called as M<Apple>?

void M<T>(T t) where T : Fruit
{
    Banana b = (Banana)t;
}

Most people reading the code would say that this should call the user-defined conversion from Apple to Banana. But C# generics are not C++ templates; the method is not recompiled from scratch for every generic construction. Rather, the method is compiled once, and during that compilation the meaning of every operator, including casts, is determined for every possible generic instantiation.

The body of M<Apple> would have to have a user-defined conversion. The body of M<Banana> would have an identity conversion. M<Cherry> would be an error. We cannot have three different meanings of an operator in a generic method, so the operator is rejected.

Instead what you have to do is:

void M<T>(T t) where T : Fruit
{
    Banana b = (Banana)(object)t;
}

Now both conversions are clear. The conversion to object is an implicit reference conversion; the conversion to Banana is an explicit reference conversion. The user-defined conversion is never called, and if this is constructed with Cherry then the error is at runtime, not compile time, as it always is when casting from object.

The as operator is not like the cast operator; it always means the same thing no matter what types it is given because the as operator does not ever invoke a user-defined conversion. Therefore it can be used in a context where a cast would be illegal.


"The as operator is like a cast operation. However, if the conversion is not possible, as returns null instead of raising an exception."

You don't receive a compile time error with the as operator because the compiler does not check for undefined explicit casts when using the as operator; its purpose is to allow attempted run-time casts that may be valid or not, and if they are not, return null rather than throw an exception.

In any case, if you plan to handle the case where fruit is not Apple, you should implement your check as

var asApple = fruit as Appple;
if(asApple == null)
{
    //oh no
}
else
{
   //yippie!
}

To answer the question why the compiler won't let you write your code like you want to. The if gets evaluated at runtime, so the compiler doesn't know that the cast only happens if it would be valid.

To get it to work you "could" do something like this in your if:

Apple apple = (Apple)(object)fruit;

Here's some more on the same question.

Of course using the as operator is the best solution.