4

I have a simple interface and two classes implement it:

public interface IMovable { }

public class Human : IMovable { }
public class Animal : IMovable { }

The following generic method results in a compile-time error: Cannot convert type 'Human' to 'T'

public static T DoSomething<T>(string typeCode) where T : class, IMovable
{
    if (typeCode == "HUM")
    {
        return (T)new Human();      // Explicit cast
    }
    else if (typeCode == "ANI")
    {
        return (T)new Animal();     // Explicit cast
    }
    else
    {
        return null;
    }
}

But when the as keyword is used, all is fine:

public static T DoSomething<T>(string typeCode) where T : class, IMovable
{
    if (typeCode == "HUM")
    {
        return new Human() as T;     // 'as'
    }
    else if (typeCode == "ANI")
    {
        return new Animal() as T;    // 'as'
    }
    else
    {
        return null;
    }
}

Why does as work but explicit cast doesn't?

Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
anar khalilov
  • 16,993
  • 9
  • 47
  • 62
  • 2
    Because `as` will simply return `null` if it cannot cast. – vgru Jun 02 '15 at 08:32
  • Variant with `as` is better, why you try to use `(T)` conversion? – General-Doomer Jun 02 '15 at 08:33
  • Also you can use `(T)((object)new Animal())` but it is ugly. – General-Doomer Jun 02 '15 at 08:34
  • @Groo, thanks for your comment. What you have specified is known to me. I am curious why it gives compile-time error? If I am using an invalid cast, I expect to get a run-time InvalidCastException, not compile-time. – anar khalilov Jun 02 '15 at 08:38
  • Duplicate of: http://stackoverflow.com/questions/4092393/value-of-type-t-cannot-be-converted-to and http://stackoverflow.com/questions/2028310/cast-from-genericst-to-specific-subclass – Stefan Steinegger Jun 02 '15 at 08:38
  • @General-Doomer, I will use the `as` version. I am just curious why I do get a compile-time error rather than a run-time error. – anar khalilov Jun 02 '15 at 08:39
  • Don't confuse something that compiles with something that works ;-) Everything that works also compiles, but not everything that compiles works. – Adam Houldsworth Jun 02 '15 at 08:39
  • @AnarKhalilov, the compiler will refuse to compile other code that throws exceptions, eg `var y = (DateTime)"";` won't compile. This feature isn't unique to generics. – David Arno Jun 02 '15 at 08:45
  • @Anar: generics might give you a false sense of security: you thing you have strongly typed code, but instead get something which may fail in runtime. For the `as` version, compiler knows that it will first check the runtime type of `T` and return `null`. For the direct cast, it's merely trying to indicate that you need to be more explicit. For example, using `(T)(object)new Human()` or `(T)(IMovable)new Human()` will pass compilation, simply because you are explicitly telling the compiler that you know what you're doing. – vgru Jun 02 '15 at 08:51

3 Answers3

6

Short answer is, because T doesn't have to be of the correct type. Compiler is really trying to help you here, because you are doing something which might easily fail in runtime.

E.g. consider what happens with:

var result = DoSomething<Human>("ANI");

Longer answer is, you shouldn't be casting at all. Casting indicates problems with your OOP design, and is especially wrong when using generics: you lose the whole point of generics, actually. Generics are supposed to allow you create a "template" which abstracts away the actual type, leaving you to worry about the algorithm itself instead of concrete types.

In this case, you probably don't need generics at all. Your method is basically a less safer way of doing this:

public static T DoSomething<T>() where T : new()
{
    return new T();
}

or this:

public static IMovable DoSomething(string typeCode)
{
    if (typeCode == "HUM")
        return new Human();

    if (typeCode == "ANI")
        return new Animal();

    return null;
}

To silence the compiler, you may also add an intermediate cast, which tells the compiler you went an extra step to indicate that you really want to cast it this way: For example, using

(T)(object)new Human()

or

(T)(IMovable)new Human() 

will both pass compilation, although the conversion from IMovable to T is no safer than the original code, and casting an object to T even unsafer. But this is not the solution to your underlying issue, which is design related.

vgru
  • 49,838
  • 16
  • 120
  • 201
5

With your code, it is perfectly possible to call DoSomething<Animal>, and then you have (Animal)new Human().
That is biologically correct, but your model does not allow it.

Do you really need generics here? Maybe you just want to return IMovable in this case.

Kobi
  • 135,331
  • 41
  • 252
  • 292
1

Under the covers, the 'as' will do an 'is' check first before attempting the cast. So it will not attempt it if it can't cast it and will then return null.

Justin Harvey
  • 14,446
  • 2
  • 27
  • 30