Ambiguous method overloads when using type type parameters

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; } } 

Compiler errors on line 9, where TeeAsync is called 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?

+5
source share
1 answer

The second parameters are not identical. They are both Func<T, Task> , but T is different in each case.

The first overload has this T source . That means when you do

 Task<string> stringTask = Task.FromResult("sample"); stringTask.TeeAsync(...) 

for the first overload, T is Task<string> .

The second has this Task<T> asyncSource . Therefore, in the above case, for the second overload T there is string .

Since you do not specify type st here:

 stringTask.TeeAsync(st => Task.CompletedTask).Wait(); 

st can be either Task<string> (first overload) or string (second). The compiler cannot know what you had in mind. If you do:

 stringTask.TeeAsync((string st) => Task.CompletedTask).Wait(); 

He will choose the second one correctly. And if you do

 stringTask.TeeAsync((Task<string> st) => Task.CompletedTask).Wait(); 

he will choose first.

Interestingly, if you really use st in such a way that the compiler can determine if it will be a string or Task<string> , it will do it. For example, this will compile and select the 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 compiles and is selected 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 again (correctly) will not be able to select overload:

 // ToString() exists on both string and Task<string> // so doesn't help compiler to choose stringTask.TeeAsync(st => Task.FromResult(st.ToString())).Wait(); 
+4
source

Source: https://habr.com/ru/post/1276191/


All Articles