Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit casting vs using 'as' inside a generic method [duplicate]

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?

like image 516
anar khalilov Avatar asked Dec 14 '22 14:12

anar khalilov


1 Answers

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.

like image 79
Groo Avatar answered May 11 '23 10:05

Groo