42

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 ?

Incognito
  • 16,567
  • 9
  • 52
  • 74

2 Answers2

59

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.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • If this is the case than shouldn't it work with class constraint ? – Incognito Jun 20 '11 at 05:48
  • @Incognito: No, I don't believe so. I don't know the details of why the direct conversion is prohibited, but the cast to `object` first shouldn't actually add any performance penalty. – Jon Skeet Jun 20 '11 at 05:58
  • Be aware that you WILL get a runtime error if you cast to object first and the type "T" is not castable as "SomeClass". So unless you really have a reason to do that, I would avoid that at all cost. – ExCodeCowboy Jun 20 '11 at 05:59
  • @Xenophile: That's precisely why I would typically *prefer* the cast over using `as` - to spot errors earlier. There's nothing specific to generics about that, of course. – Jon Skeet Jun 20 '11 at 06:00
  • @Jon yes it is possible to have casting using intermediate object instance. This is the practical solution for the current situation. Personally I am more interested not in the solution, but in the inner reasons, why and what. – Incognito Jun 20 '11 at 06:02
  • @Incognito Really the correct way to fix it is like jwJung specified using type constraints. The compiler is attempting to validate conversions in the method. The "(object)" cast simply short curcuits this because it only checks each cast step, not the full chain. – ExCodeCowboy Jun 20 '11 at 06:07
  • @Incognito: I've updated my answer with a spec reference which goes into some of the reasons. – Jon Skeet Jun 20 '11 at 06:14
  • @Xenophile: That's only the correct way to fix it if that's actually a possibility in the bigger picture. We don't know what the bigger picture *is* here - it's possible that the constraint can't be applied for some other reason. It's usually better to avoid casts where possible, but it's not always possible. – Jon Skeet Jun 20 '11 at 06:14
3

In the inheritance principle of C#, interfaces could be inherited multiple times, but a class just once. As the inheritance from interfaces has complex hierarchy, the .net framework does not need to ensure the generic type T a specific interface at the compile time.(EDIT) On the contrary, a class could be ensured a specific class with declaring a type constraint at the compile as the following code.

class MyClass<T> where T : SomeClass
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;
      SomeClass      obj2 = (SomeClass)t;     
   }
}
Jin-Wook Chung
  • 4,196
  • 1
  • 26
  • 45