简体   繁体   中英

Ambiguous method overloads when using generic 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;
    }
}

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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM