You have a slight misinterpretation of how Func<Tin, Tout> works. See the docs :
public delegate TResult Func<in T, out TResult>( T arg )
The first argument is the parameter, and the last argument is the return type.
When you look at this simplified version of your code:
internal class Program { public static void Main(string[] args) { new MyClass<double, double>(Method); } private static double Method(double d) { throw new NotImplementedException(); } } internal class MyClass<T, U> { public MyClass(Func<U, T> arg) { } public MyClass(Func<U, Task<T>> arg) { } }
You will notice that both arguments first indicate double , which is the argument, and then they differ in return type: T vs Task<T> .
However, as we know, overloading is based on the method name, arty parameter, and parameter types. Return types are completely ignored. In our case, this means that we have two Func<Tin, Tout> with double as an argument and T vs Task<T> as the return type.
Switching arguments around compilation is just fine:
internal class MyClass<T, U> { public MyClass(Func<T, U> arg) { } public MyClass(Func<Task<T>, U> arg) { } }
If you look in Visual Studio, you will notice that this method is now grayed out, which makes sense because the Method argument is of type double and as such will always match T , not Task<T> .
So, to verify that it will now fall into the correct overload, if you pass another asynchronous method, you can add a second method:
private static double MethodAsync(Task<double> d) { throw new NotImplementedException(); }
and name it with
new MyClass<double, double>(MethodAsync);
Now you will notice that the asynchronous Func<Task<T>, U>> gets in (which you can check by simply printing to the console from the designers).
In short: you are trying to perform overload resolution by return type, which is obviously not possible.