Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior of "as" operator, it differs from plain cast

Tags:

c#

.net

casting

I asked this question. This code doesn't compile ("Cannot convert Generic<T> to T") because of the reason explained here (even if I'd expect an InvalidCastException at run-time instead of a compile-time error).

class NonGeneric
{
}

class Generic<T> : NonGeneric
    where T : NonGeneric
{
    T DoSomething()
    {
        return (T)this; // ** Cannot convert...
    }
}

Accepted solution gave this workaround:

T DoSomething()
{
    return this as T;
}

My question is: why? as operator should be exactly equivalent to cast operator:

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

If this as T should be equivalent to this is T? (T)this: (T)null then why as T works and (T)this doesn't even compile? AFAIK cast could be used in a more wide range of situations than as:

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.

Then why this? Is it a documented feature of as operator? Is it a compiler/language limitation with generic types? Note that this code compiles fine:

return (T)((object)this);

Is this because compiler can't be sure if T is dynamic (even if there is a where constraint) then it'll always generate such code?

like image 856
Adriano Repetti Avatar asked Jan 29 '14 13:01

Adriano Repetti


1 Answers

It says in the C# Language Specification (emphasis mine),

If the compile-time type of E is not dynamic, the operation E as T produces the same result as E is T ? (T)(E) : (T)null except that E is only evaluated once. The compiler can be expected to optimize E as T to perform at most one dynamic type check as opposed to the two dynamic type checks implied by the expansion above.

If the compile-time type of E is dynamic, unlike the cast operator the as operator is not dynamically bound (§7.2.2). Therefore the expansion in this case is: E is T ? (T)(object)(E) : (T)null

This seems to be the reason why the compilation succeed using as or when this is cast to an object first. Furthermore,

In an operation of the form E as T, E must be an expression and T must be a reference type, a type parameter known to be a reference type, or a nullable type. Furthermore, at least one of the following must be true, or otherwise a compile-time error occurs:

• An identity (§6.1.1), implicit nullable (§6.1.4), implicit reference (§6.1.6), boxing (§6.1.7), explicit nullable (§6.2.3), explicit reference (§6.2.4), or unboxing (§6.2.5) conversion exists from E to T.

The type of E or T is an open type.

E is the null literal.

Which is the current case with your generic class.

like image 198
rae1 Avatar answered Sep 18 '22 13:09

rae1