Consider the following example:
class Test
{
public void Fun<T>(Func<T, T> f)
{
}
public string Fun2(string test)
{
return "";
}
public Test()
{
Fun<string>(Fun2);
}
}
This compiles well.
I wonder why cannot I remove <string>
generic argument? I get an error that it cannot be inferred from the usage.
I understand that such an inference might be challenging for the compiler, but nevertheless it seems possible.
I would like an explanation of this behaviour.
Edit answering Jon Hanna's answer:
Then why this works?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
Here I bind only one parameter with T1 a
, but T2
seems to be similarly difficult.
It can't infer the type, because the type isn't defined here.
Fun2
is not a Func<string, string>
, it is though something that can be assigned to Func<string, string>
.
So if you use:
public Test()
{
Func<string, string> del = Fun2;
Fun(del);
}
Or:
public Test()
{
Fun((Func<string, string>)Fun2);
}
Then you are explicitly creating a Func<string, string>
from Fun2
, and generic type-inference works accordingly.
Conversely, when you do:
public Test()
{
Fun<string>(Fun2);
}
Then the set of overloads Fun<string>
contains only one that accepts a Func<string, string>
and the compiler can infer that you want to use Fun2
as such.
But you're asking it to infer both the generic type based on the the type of the argument, and the type of the argument based on the generic type. That's a bigger ask than either of the types of inference it can do.
(It's worth considering that in .NET 1.0 not only were delegates not generic—so you would have had to define delgate string MyDelegate(string test)
—but it was also necessary to create the object with a constructor Fun(new MyDelegate(Fun2))
. The syntax has changed to make use of delegates easier in several ways, but the implicit use of Fun2
as Func<string, string>
is still a construction of a delegate object behinds the scenes).
Then why this works?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
Because then it can infer, in order:
T1
is int
.Fun2
is being assigned to Func<int, T2>
for some T2
.Fun2
can be assigned to a Func<int, T2>
if T2
is string
. Therefore T2
is string.In particular, the return type of Func
can be inferred from a function once you have the argument types. This is just as well (and worth the effort on the part of the compiler) because it's important in Linq's Select
. That brings up a related case, the fact that with just x.Select(i => i.ToString())
we don't have enough information to know what the lambda gets cast to. Once we know whether the x
is IEnumerable<T>
or IQueryable<T>
we know we either have Func<T, ?>
or Expression<Func<T, ?>>
and the rest can be inferred from there.
It's also worth noting here, that deducing the return type is not subject to an ambiguity that deducing the other types are. Consider if we had both your Fun2
(the one that takes a string
and the one that takes an int
) in the same class. This is valid C# overloading, but makes deduction of the type of Func<T, string>
that Fun2
can be cast to impossible; both are valid.
However, while .NET does allow overloading on return type, C# does not. So no valid C# program can be ambiguous on the return type of a Func<T, TResult>
created from a method (or lambda) once the type of T
is determined. That relative ease, combined with the great usefulness, makes it something the compiler is well to infer for us.
You are wanting the compiler to infer string
from Fun2
, which is too much of an ask for the C# compiler. This is because it sees Fun2
as a method group, not a delegate, when referenced in Test
.
If you change the code to pass in the parameter needed by Fun2
and invoke it from Fun
, then the need goes away, as you now have a string
parameter, allowing the type to be inferred:
class Test
{
public void Fun<T>(Func<T, T> f, T x)
{
f(x);
}
public string Fun2(string test)
{
return test;
}
public Test()
{
Fun(Fun2, "");
}
}
To answer your edited version of the question, by supplying the T1
type you have now supplied the compiler - via the Fun(0, Fun2);
call - extra information. It now knows it needs a method, from the Fun2
method group, that has a T1
parameter, in this case int
. This narrows it down to one method and so it can infer which one to use.`
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