``I've run across an interesting curiosity when compiling some C# code that uses generics with type constraints. I've written a quick test case for illustration. I'm using .NET 4.0 with Visual Studio 2010.
namespace TestCast
{
public class Fruit { }
public class Apple : Fruit { }
public static class Test
{
public static void TestFruit<FruitType>(FruitType fruit)
where FruitType : Fruit
{
if (fruit is Apple)
{
Apple apple = (Apple)fruit;
}
}
}
}
The cast to Apple fails with the error: "Cannot convert type 'FruitType' to 'TestCast.Apple'". However, if I change the line to use the as
operator, it compiles without error:
Apple apple = fruit as Apple;
Could someone please explain why this is the case?
The as operator never throws an exception, and if the conversion is not possible, it will return null. Example: In this example, we only have one cast on each block of code, and also this approach allows you to avoid any exceptions that may occur at execution time.
DirectCast() is more strict than the C# casting operator. It only allows you to cast when the item being cast already is the type you are casting to. It won't do any conversion. Use DirectCast if you're absolutely positively sure that an object is the specified type and the run-time type of the expression are the same.
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!
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With