使用泛型类型参数时有歧义的方法重载
Ambiguous method overloads when using generic type parameters
考虑以下程序:
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;
}
}
第 9 行的编译器错误,其中 TeeAsync
在 stringTask
上调用,因为
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>)'
从每个重载中删除第二个参数突然允许编译器区分第一个参数的 Task<T>
和 T
。但为什么第二个参数——两个重载之间相同——导致编译器感到困惑?
第二个参数不相同。它们都是 Func<T, Task>
,但 T
各不相同。
第一次重载 this T source
。这意味着当你做
Task<string> stringTask = Task.FromResult("sample");
stringTask.TeeAsync(...)
对于第一次重载,T
是 Task<string>
。
其次有this Task<T> asyncSource
。所以在上面的例子中,对于第二个重载 T
是 string
.
因为你没有在这里指定 st
的类型:
stringTask.TeeAsync(st => Task.CompletedTask).Wait();
st
可以是 Task<string>
(第一次重载)或 string
(第二次)。编译器无法知道您指的是哪一个。如果你这样做:
stringTask.TeeAsync((string st) => Task.CompletedTask).Wait();
它会正确选择第二个。如果你这样做
stringTask.TeeAsync((Task<string> st) => Task.CompletedTask).Wait();
会优先选择
有趣的是,如果您实际使用 st
的方式允许编译器推断它是 string
还是 Task<string>
- 它会这样做。例如,这将编译并选择第二个重载:
// we don't specify st type, but using Length property
// which only exists on string
stringTask.TeeAsync(st => Task.FromResult(st.Length)).Wait();
这将首先编译并选择:
// we don't specify st type, but using Result property
// which only exists on Task<string>
stringTask.TeeAsync(st => Task.FromResult(st.Result)).Wait();
但是如果你使用两者都存在的东西,它会再次(正确地)无法选择重载:
// ToString() exists on both string and Task<string>
// so doesn't help compiler to choose
stringTask.TeeAsync(st => Task.FromResult(st.ToString())).Wait();
考虑以下程序:
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;
}
}
第 9 行的编译器错误,其中 TeeAsync
在 stringTask
上调用,因为
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>)'
从每个重载中删除第二个参数突然允许编译器区分第一个参数的 Task<T>
和 T
。但为什么第二个参数——两个重载之间相同——导致编译器感到困惑?
第二个参数不相同。它们都是 Func<T, Task>
,但 T
各不相同。
第一次重载 this T source
。这意味着当你做
Task<string> stringTask = Task.FromResult("sample");
stringTask.TeeAsync(...)
对于第一次重载,T
是 Task<string>
。
其次有this Task<T> asyncSource
。所以在上面的例子中,对于第二个重载 T
是 string
.
因为你没有在这里指定 st
的类型:
stringTask.TeeAsync(st => Task.CompletedTask).Wait();
st
可以是 Task<string>
(第一次重载)或 string
(第二次)。编译器无法知道您指的是哪一个。如果你这样做:
stringTask.TeeAsync((string st) => Task.CompletedTask).Wait();
它会正确选择第二个。如果你这样做
stringTask.TeeAsync((Task<string> st) => Task.CompletedTask).Wait();
会优先选择
有趣的是,如果您实际使用 st
的方式允许编译器推断它是 string
还是 Task<string>
- 它会这样做。例如,这将编译并选择第二个重载:
// we don't specify st type, but using Length property
// which only exists on string
stringTask.TeeAsync(st => Task.FromResult(st.Length)).Wait();
这将首先编译并选择:
// we don't specify st type, but using Result property
// which only exists on Task<string>
stringTask.TeeAsync(st => Task.FromResult(st.Result)).Wait();
但是如果你使用两者都存在的东西,它会再次(正确地)无法选择重载:
// ToString() exists on both string and Task<string>
// so doesn't help compiler to choose
stringTask.TeeAsync(st => Task.FromResult(st.ToString())).Wait();