explicitly cast generic type parameters to any interface

Incognito picture Incognito · Jun 20, 2011 · Viewed 36.7k times · Source

In Generics FAQ: Best Practices says :

The compiler will let you explicitly cast generic type parameters to any interface, but not to a class:

interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T> 
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;//Compiles
      SomeClass      obj2 = (SomeClass)t;     //Does not compile
   }
}

I see limitation reasonable for both, classes and interfaces, unless the class/interface is not specified as constraint type.

So why such behavior, why it is allowed for interfaces ?

Answer

Jon Skeet picture Jon Skeet · Jun 20, 2011

I believe this is because the cast to SomeClass can mean any number of things depending on what conversions are available, whereas the cast to ISomeInterface can only be a reference conversion or a boxing conversion.

Options:

  • Cast to object first:

      SomeClass obj2 = (SomeClass) (object) t;
    
  • Use as instead:

      SomeClass obj2 = t as SomeClass;
    

Obviously in the second case you would also need to perform a nullity check afterwards in case t is not a SomeClass.

EDIT: The reasoning for this is given in section 6.2.7 of the C# 4 specification:

The above rules do not permit a direct explicit conversion from an unconstrained type parameter to a non-interface type, which might be surprising. The reason for this rule is to prevent confusion and make the semantics of such conversions clear. For example, consider the following declaration:

class X<T>
{
    public static long F(T t) {
        return (long)t; // Error 
    }
} 

If the direct explicit conversion of t to int were permitted, one might easily expect that X<int>.F(7) would return 7L. However, it would not, because the standard numeric conversions are only considered when the types are known to be numeric at binding-time. In order to make the semantics clear, the above example must instead be written:

class X<T>
{
    public static long F(T t) {
        return (long)(object)t; // Ok, but will only work when T is long
    }
}

This code will now compile but executing X<int>.F(7) would then throw an exception at run-time, since a boxed int cannot be converted directly to a long.