Consider the following program:
using System;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
var stringTask = Task.FromResult("sample");
stringTask.TeeAsync(st => Task.CompletedTask).Wait();
}
}
public static class FunctionalExtensions
{
public static async Task<T> TeeAsync<T>(this T source, Func<T, Task> asyncAction)
{
await Task.Delay(0); // todo: do something with source
return source;
}
public static async Task<T> TeeAsync<T>(this Task<T> asyncSource, Func<T, Task> asyncAction)
{
var source = await asyncSource;
await Task.Delay(0); // todo: do something with source
return source;
}
}
The compiler errors on line 9 where TeeAsync
is invoked on stringTask
because
The call is ambiguous between the following methods or properties: 'FunctionalExtensions.TeeAsync<T>(T, Func<T, Task>)' and 'FunctionalExtensions.TeeAsync<T>(Task<T>, Func<T, Task>)'
Removing the second parameter from each overload suddenly allows the compiler to distinguish between Task<T>
and T
for the first parameter. But why does the second parameter -- identical between the two overloads -- cause the compiler to get confused?
Second parameters are not identical. They are both Func<T, Task>
, but T
is different in each case.
First overload has this T source
. That means when you do
Task<string> stringTask = Task.FromResult("sample");
stringTask.TeeAsync(...)
for first overload, T
is Task<string>
.
Second has this Task<T> asyncSource
. So in above case, for second overload T
is string
.
Because you don't specify type of st
here:
stringTask.TeeAsync(st => Task.CompletedTask).Wait();
st
can be either Task<string>
(first overload) or string
(second). Compiler cannot know which one you meant. If you do:
stringTask.TeeAsync((string st) => Task.CompletedTask).Wait();
It will correctly choose second one. And if you do
stringTask.TeeAsync((Task<string> st) => Task.CompletedTask).Wait();
it will choose first.
Interesting that if you actually use st
in a way which will allow compiler to deduce whether it's string
or Task<string>
- it will do that. For example this will compile and choose second overload:
// we don't specify st type, but using Length property
// which only exists on string
stringTask.TeeAsync(st => Task.FromResult(st.Length)).Wait();
And this will compile and choose first:
// we don't specify st type, but using Result property
// which only exists on Task<string>
stringTask.TeeAsync(st => Task.FromResult(st.Result)).Wait();
But if you use something that exists on both, it will again (correctly) fail to choose an overload:
// ToString() exists on both string and Task<string>
// so doesn't help compiler to choose
stringTask.TeeAsync(st => Task.FromResult(st.ToString())).Wait();
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