4

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.

user11909
  • 1,235
  • 11
  • 27
Joshua Frank
  • 13,120
  • 11
  • 46
  • 95
  • 1
    Note that you **usually** don't actually want to subclass List<> directly... see some good arguments here: https://stackoverflow.com/questions/21692193/why-not-inherit-from-listt – Milney Aug 27 '19 at 11:27
  • I've read that and find much of it compelling, but sometimes this still seems like the best way to express my semantics. – Joshua Frank Aug 27 '19 at 11:29

2 Answers2

5

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.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Hmm, I guess I may have been mislead by situations like `public static IEnumerable Select(this IEnumerable source, Func selector);` where it infers all the types, but some from the source list and the others from the `selector` function. – Joshua Frank Aug 27 '19 at 11:32
  • @JoshuaFrank Well, those are all arguments, so that should work indeed. – Patrick Hofman Aug 27 '19 at 11:35
  • Do you know why it *doesn't* infer some arguments even if it can't get all of them? – Joshua Frank Aug 27 '19 at 11:37
  • "Because it is in the specs" is the obvious answer, but practically I think it makes method (overload) resolution virtually impossible if you start guessing have the type arguments. – Patrick Hofman Aug 27 '19 at 11:38
2

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.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • I get this, but why can't it infer that `TFruit` is a Banana and let me specify only that `TPeeledFruit` is `PeeledFruit` with syntax like `Peel(myBanana)` in other words, letting it infer what it can and requiring that I specify the other types. It seems it has to infer all or none. – Joshua Frank Aug 27 '19 at 11:41
  • @JoshuaFrank Oh it could very well do that if somebody had bothered to consider it in the design of the language. The fact is that partial inference is not supported and I'm guessing there are good reasons for it; to begin with, easily understandbale code suddenly can get really messy and hard to follow because you'd need to figure out, on *every* generic call with explicit type arguments, if its fully explicit or a different method altogether with partially inferred types... ugh, nightmare. – InBetween Aug 27 '19 at 11:44
  • I guess that's right. Thanks for the detailed info. – Joshua Frank Aug 27 '19 at 12:18