Unexpected behavior of “as” operator, it differs f

2020-07-06 07:13发布

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?

标签: c# .net casting
3条回答
太酷不给撩
2楼-- · 2020-07-06 07:50

'as' operator in C# performs these actions:-

  1. It returns null if given variable is not of given type of any of its base types. It doesnt throw any exception
  2. Can be only applied with reference type variables.
  3. 'as' does not do any conversion (implicit/explicit)

'as' operator is slightly faster than any casting (even in cases where there are no invalid casts which would severely lower casting's performance due to exceptions).

查看更多
趁早两清
3楼-- · 2020-07-06 07:56

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.

查看更多
孤傲高冷的网名
4楼-- · 2020-07-06 07:57

According to msdn:-

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

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

Another difference is that:-

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.

查看更多
登录 后发表回答