18

When attempting to use the C# "as" keyword against a non-generic type that cannot be cast to, the compiler gives an error that the type cannot be converted.

However when using the "as" keyword against a generic type the compiler gives no error:

public class Foo { }

public class Bar<T> { }

public class Usage<T> {
   public void Test() {
      EventArgs args = new EventArgs();
      var foo = args as Foo;     // Compiler Error: cannot convert type
      var bar = args as Bar<T>;  // No compiler error
   }
}

I discovered this behaviour in a much larger code base where the lack of a compile time error led to an issue at runtime.

Is the conflicting behaviour by design? If so, does anyone have any insight as to why?

Phillip Trelford
  • 6,513
  • 25
  • 40
  • 1
    FYI: R# does flag it with the warning "Suspicious cast: there is no type in the solution that is inherited from both 'EventArgs' and 'Bar'" – juharr Jan 26 '15 at 12:23
  • @juharr thanks! I guess it goes to show that the issue is detectable in C#. I also double checked the issue against the F# compiler which like R# picks up the invalid cast (but in the case of F# it is marked as an error) – Phillip Trelford Jan 26 '15 at 12:48

2 Answers2

8

In §7.10.11 The as operator C# 5.0 Specification says:

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.

So args as Foo gives an error because none of this is true. But in the second case, Bar<T> is an open type, and the spec explains open type as, §4.4.2 Open and closed types :

An open type is a type that involves type parameters. More specifically:

  • A type parameter defines an open type. [...]
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • 1
    I just found it in spec, but not sure what makes an open type special in this case. – Sriram Sakthivel Jan 26 '15 at 12:34
  • thanks! This answers the question "is the conflicting behaviour by design" with a yes. And the insight into why seems to be the naive implementation of the translation of the expression you mention from the documentation. – Phillip Trelford Jan 26 '15 at 12:51
  • but I should note that I'm not 100% sure that it's true. that's the only logical explanation I can think of. – Selman Genç Jan 26 '15 at 13:21
  • From what you've said I don't think it is translated as `args is Bar ? (Bar)(object)args : (Bar)null; ` since that would require `args` to be of type `dynamic` which it isn't. I've not got the spec (no office installed) so I'm only interpreting second hand. – Chris Jan 26 '15 at 14:43
-3

"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."

https://msdn.microsoft.com/en-us/library/cscsdfbt.aspx