Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't type inference work in this situation?

Tags:

c#

Suppose I have a POCO and a List class for them:

class MyClass
{...}

class MyClasses : List<MyClass> 
{...}

And the following method to map an IEnumerable<MyClass> to a MyClasses list:

public static TListType ToListOfType<TListType, TItemType>(this IEnumerable<TItemType> list) where TListType : IList<TItemType>, new()
{
    var ret = new TListType();

    foreach (var item in list)
    {
        ret.Add(item);
    }

    return ret;
}

I would expect this code to compile, but it doesn't:

var list = someListOfMyClass.ToListOfType<MyClasses>();

but instead I get

Error CS1061 'IEnumerable' does not contain a definition for 'ToListOfType' and no accessible extension method 'ToListOfType' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)

However, this does work:

var list = someListOfMyClass.ToListOfType<MyClasses, MyClass>();

I don't understand why type inference isn't sufficient for the compiler to know what the item type is, since the this variable is a list of a known type.

like image 948
Joshua Frank Avatar asked Mar 04 '23 12:03

Joshua Frank


2 Answers

Type inference doesn't infer the missing arguments from a generic method call. Instead it either infers all or none of the arguments. So you can't call the method with one type argument and expect the compiler to come up with the rest.

In this case, it is possible to infer TItemType, since it is in one of the arguments. TListType can't be inferred though since it is the return type. So in the end, the method signature can't be inferred, and you have to specify all the type arguments.

like image 76
Patrick Hofman Avatar answered Mar 11 '23 07:03

Patrick Hofman


As other have said, partial generic type argument inference is not supported by c#.

About why one of the types can't be inferred, maybe a more obvious example makes it clearer:

TPeeledFruit peeled = Peel<TPeeledFruit, TFruit)(
    this TFruit fruit) where TPeeledFruit: TFruit

Ok, now you say:

var myPeeledBanana = Peel(myBanana)

The compiler infers easily enough that TFruit must be Banana.

But how is it going to ever infer what TPeeledFruit is? It has no information whatsoever of that type; you might see it as obvious because you understand the relation but the compiler has no such knowledge. The only thing it knows is that TPeeledFruit must be a type that inherits from TFruit but that can be infinite amount of types: it can be Banana again, it can be PeeledBanana, it can be PeeledRipeBanana, PeeledGreenBanana, etc.

Also consider the fact that explicitly typing the assignment helps in no way whatsoever:

PeeledBanana myPeeledBanana = Peel(myBanana)

This wont work either, c# reasons types on the right hand side of an assignment first and then works out if the assignment is in fact legal. If its an implicitly typed variable then the assignment is always valid.

like image 24
InBetween Avatar answered Mar 11 '23 06:03

InBetween