I know there are several posts already concerning the difference between casts and the as
operator. They all mostly restate the same facts:
- The
as
operator will not throw, but returnnull
if the cast fails - Consequently, the
as
operator only works with reference types - The
as
operator will not use user-defined conversion operators
Answers then tend to debate endlessly the how to use or not use the one or the other and the pros and cons of each, even their performance (which interests me not at all).
But there is something more at work here. Consider:
static void MyGenericMethod<T>(T foo)
{
var myBar1 = foo as Bar; // compiles
var myBar2 = (Bar)foo; // does not compile ('Cannot cast expression of
// type 'T' to type 'Bar')
}
Please never mind whether this obviously contrite example is good practice or not. My concern here is the very interesting disparity between the two in that the cast will not compile whereas the as
does. I really would like to know if anyone could shed some light on this.
As is often noted, the as
operator disregards user-defined conversions, but in the above example, it is clearly the more capable of the two. Note that as
far as the compiler is concerned, there is no known connection between the (unknown at compile-time) type T and Bar. The cast is entirely 'run-time'. Should we suspect that the cast is resolved, wholly or partly, at compile time and the as
operator not?
By the way, adding a type constraint unsurprisingly fixes the cast, thus:
static void MyGenericMethod<T>(T foo) where T : Bar
{
var myBar1 = foo as Bar; // compiles
var myBar2 = (Bar)foo; // now also compiles
}
Why does the as
operator compile and the cast not?
foo as Bar
is specifying the type parameter, whereas(Bar)foo
is attempting a cast on an unspecified typeT
. That's why the second example works; you've constrained the type parameter to the castable type. The first example doesn't compile becausefoo
is not guaranteed to be type-safely castable toBar
. – MoriceInvalidCastException
, or theas
shouldn't compile either. – BowknotT
as the thing to cast, there's no compile-time guarantee that the actual type you specify at run-time is going to be castable toBar
. Generics are compile-time constructs; the check for type safety is done at compile time. Since(Bar)foo
cannot be checked for type safety at compile time, a compile error occurs. But using theas
operator specifies the type; it constrains the run-time type to beBar
. So no compile error occurs, because the compiler can deduce thatBar
is castable. – Moriceobject
does not require compile-time type safety. Working with genericT
parameters does. – Morice