12
votes

I have conducted the following inference tests:

static class InferenceTest {
    static void TakeInt(int a) { }
    static int GiveInt() { return 0; }
    static int TakeAndGiveInt(int a) { return 0; }

    static void ConsumeAction1<T>(Action<T> a) { }
    static void ConsumeFunc1<T>(Func<T> f) { }
    static void ConsumeFunc2a<T1, T2>(Func<T1, T2> f) { }
    static void ConsumeFunc2b<T>(Func<int, T> f) { }
    static void ConsumeFunc2c<T>(Func<T, T> f) { }
    static void ConsumeFunc1Func2<T1, T2>(Func<T1> f1, Func<T1, T2> f2) { }

    static void Main() {
        ConsumeAction1(TakeInt);        //error
        ConsumeFunc1(GiveInt);          //ok
        ConsumeFunc2a(TakeAndGiveInt);  //error
        ConsumeFunc2b(TakeAndGiveInt);  //ok
        ConsumeFunc2c(TakeAndGiveInt);  //error
        ConsumeFunc1Func2(GiveInt, TakeAndGiveInt); //ok
    }
}

The results seem to suggest that the C# compiler is unable to infer the generic type arguments for the delegate function parameters from a non-generic method group.

What puzzles me the most is that C# is can infer the type arguments for Func<T1, T2> from the method return values in ConsumeFunc1Func2, but is unable to infer the types for Func<T, T> in ConsumeFunc2c.

This question is similar to the T of Func<S, T> is inferred from output of lambda expression only when S and T are different? question, but instead of lambdas with unknown parameter types we have non-generic method groups.

The Why can't C# infer type from this seemingly simple, obvious case question sort of answers the questions "Why are non-ambiguous non-generic methods not enough for inference?" and "Why is there a difference between the argument types and the return value type for inference?".

Questions:

Why can the C# compiler infer the type of Func<T> using the type of the return value, but fails to see the success in the Func<T, T> case?

Why can the C# compiler infer the T1 type argument for Func<T1, T2> from the Func<T1> in ConsumeFunc1Func2, but cannot infer the T type argument for Func<T, T> from itself in ConsumeFunc2c which seems to be easier?

2
In the ConsumeFunc1Func2, the compile is still only inferring from return value, not parameter types. T1 is resolved from the return value of GiveInt, and T2 is resolved from the return value of TakeAndGiveInt. So there's no extra mystery added by the ConsumeFunc1Func2 case.Baldrick
I would have a good read of section 7.5.2 of the C# 4.0 spec. It's quite readable, and describes the various phases of type inference, and how they related to method groups.Baldrick
ConsumeFunc2b shows that for Func<?, T> the return type T can be resolved from TakeAndGiveInt. But when ? is also T as is the case with Func<T, T> in ConsumeFunc2c, the compiler seems to forget the parameter T is the same as the already inferred Ts. Quite unlike the ConsumeFunc1Func2 success.Ark-kun

2 Answers

3
votes

In general, a method name will not uniquely identify a unique type Action<T> to which the method group could be assigned. For example, even if there's only one overload of Fred and it takes a single Cat argument, that overload could be assigned not just to an Action<Cat>, but also to some other types like Action<Mammal>, Action<Animal>, or Action<Object>. While there are some cases where one type substitution would be in every way superior to any alternative, that is not always the case. It's cleaner to define the language to require that the type of delegate be specified, than to have the compiler try to "guess", especially since having the compiler guess would mean that many things which shouldn't be breaking changes, would be (e.g. adding a method overload may render ambiguous a type inference which used to work).

1
votes

Method parameters are not inspected.

As suggested, in ConsumeFunc1Func2 the compiler is inferring only from return values. In ConsumeFunc2c, TakeAndGiveInt signature is not inspected to see if its method parameter type is actually of the same type of the method return type cause... method parameters are not inspected!