简体   繁体   中英

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?

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.

It's explain in msdn doc

The as operator is like a cast operation. However, if the conversion isn't possible, as returns null instead of raising an exception. Consider the following example:

expression as type The code is equivalent to the following expression except that the expression variable is evaluated only one time.

expression is type ? (type)expression : (type)null Note that the as operator performs only reference conversions, nullable conversions, and boxing conversions. The as operator can't perform other conversions, such as user-defined conversions, which should instead be performed by using cast expressions.

It is possible for a variable of a base class type to hold a derived type. To access the derived type's method, it is necessary to cast the value back to the derived type. Using as will prevent you from getting an InvalidCastException. If you want to handle specific null reference scenario's you can do this.

public class Fruit
{
    public static explicit operator bool(Fruit D)
    {
         // handle null references
         return D.ToBoolean();
    }

    protected virtual bool ToBoolean()
    {
         return false;
    }
}

The AS Operator Keyword inherits its operation from Visual Basic.

Those in the know will tell you that Visual Basic is a more capable language than C#, which itself is an ongoing attempt to make a C-like syntax language but with Visual Basic's functionality

This is because C syntax languages are more popular with professionals, as are languages that don't advertise themselves as being Basic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM