Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 606
hatch22 Avatar asked Oct 03 '13 18:10

hatch22


People also ask

Which of the following operator does not throw an exception if the cast fails?

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.

What is direct cast in C#?

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.


2 Answers

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.

like image 183
Eric Lippert Avatar answered Oct 12 '22 12:10

Eric Lippert


"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!
}
like image 44
Preston Guillot Avatar answered Oct 12 '22 12:10

Preston Guillot