开发者

C# generics - why is an explicit cast needed to from a concrete type back to T?

开发者 https://www.devze.com 2023-04-13 01:54 出处:网络
This has been bothering me for a while. I\'m having a little trouble understanding why explicit casts are needed in the following code:

This has been bothering me for a while. I'm having a little trouble understanding why explicit casts are needed in the following code:

public static class CastStrangeness
{
    public class A
    {
    }

    public class B
        : A
    {
    }

    public static void Foo<T>(T item)
        where T : A
    {
        // Works开发者_运维知识库.
        A fromTypeParameter = item;

        // Does not compile without an explicit cast.
        T fromConcreteType = (T)fromTypeParameter;

        // Does not compile without an explicit cast.
        Foo<T>((T)fromTypeParameter);
    }

    public static void Bar(A item)
    {
        // Compiles.
        Foo<A>(item);
    }
}

It seems to me that T is guaranteed to be an A, so surely the compiler could infer that any instance of A is guaranteed to be assignable to T? Otherwise, I wouldn't be able to pass an A or a B into Foo(). So what am I missing?

PS. I've tried searching for endless permutations of keywords on this, but every result seems to wind up referring to covariance and contravariance WRT generic interfaces :)


Take the case of:

Foo(new B());

Your first assignment is ok:

A fromtypeParameter = item;

Since B : A.

But this assignment is not ok:

T fromConcreteType = fromTypeParameter;

Because you could very well have assigned fromTypeParameter as:

fromTypeParameter = new A();

Which you obviously cannot cast to T (which is B in this instance). T is more specific than A, it could be derived from A. So you can go one way but not the other, without an explicit cast (which may fail).


It seems to me that T is guaranteed to be an A, so surely the compiler could infer that any instance of A is guaranteed to be assignable to T?

Well, no.

string is an object. But

string s = new object();

is illegal.


All T's might be A... but not all A's are T.

When you create a type that derives from A and pass it into the generic method, the compiler knows it as T, the derived type. If you do not cast it back it does not know that you want A or T, or any type in the inheritance tree between T and A.

This logic applies whether you are using generics or not.

public class A
{}

public class B : A
{}

public class C: B
{}

A animal = new C();
C cat = animal;   // wont compile as it does not know that A is a cat, 
                  // you have to cast even though it looks like 
                  // a cat from the new C();

Even though you have the generic constraint, this tend to apply to the way the generic method is used and prevents the constraint to be broken. It does not apply to any referencing of a derived type through a base reference. Although the compiler 'could' figure it out it may not know that is what your intent was, so it is better for it to be safe and not infer it.


It is less subtle if you use more specific type and variable names:

public class Animal
{}

public class Giraffe : Animal
{}

public static void Foo<TAnimal>(TAnimal animal) where TAnimal : Animal
{
    Animal generalAnimal = animal;

    TAnimal possiblyMoreSpecificAnimal = (TAnimal) generalAnimal;

    // The above line only works with a cast because the compiler doesn't know
    // based solely on the variable's type that generalAnimal is a more
    // specific animal.
}
0

精彩评论

暂无评论...
验证码 换一张
取 消