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?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With