【问题标题】:Ambiguous method overloads when using generic type parameters使用泛型类型参数时不明确的方法重载
【发布时间】:2018-03-30 18:38:36
【问题描述】:

考虑以下程序:

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 行出现编译器错误,其中在 stringTask 上调用了 TeeAsync,因为

以下方法或属性之间的调用不明确:'FunctionalExtensions.TeeAsync(T, Func)' 和 'FunctionalExtensions.TeeAsync(Task, Func)'

从每个重载中删除第二个参数突然允许编译器区分Task&lt;T&gt;T 第一个参数。但是为什么第二个参数——两个重载之间相同——导致编译器感到困惑呢?

【问题讨论】:

  • @maccettura 将编译器错误消息从代码示例更改为块引用导致错误消息的某些细节被隐藏,例如错误消息中引用的方法和类的泛型类型参数。
  • 我没有看到任何变化,除了格式。查看编辑历史,我删除了 4 个空格字符并添加了&gt;。任何不在你问题中的东西都是因为它从来没有。
  • 我知道你编辑了什么。我以前看过。现在请查看您在编辑视图中看到的实际文本,并与您在此渲染页面上看到的内容进行比较。缺少类型。相信我。
  • 啊,我错过了。我以为您在谈论不存在的额外信息(例如错误消息本身的更多信息)。现在好看吗?您的代码块以前很难阅读,因为它只有一个长的可滚动字段,现在我们可以在视口中看到所有内容。
  • 你现在看起来。烦人的块引用需要特殊的编码。

标签: c# .net generics overload-resolution


【解决方案1】:

第二个参数不相同。它们都是Func&lt;T, Task&gt;,但T 在每种情况下都不同。

第一个重载有this T source。这意味着当你这样做时

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

对于第一次重载,TTask&lt;string&gt;

第二个有this Task&lt;T&gt; asyncSource。所以在上述情况下,第二次重载Tstring

因为您没有在此处指定st 的类型:

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

st 可以是Task&lt;string&gt;(第一次过载)或string(第二次)。编译器无法知道您的意思。如果你这样做:

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

它会正确选择第二个。如果你这样做了

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

它会先选择。

有趣的是,如果您实际使用st 的方式允许编译器推断它是string 还是Task&lt;string&gt; - 它会这样做。例如这将编译并选择第二个重载:

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

【讨论】:

  • 您对重构方法本身有什么建议,以避免指定 T 类型的调用开销吗?
  • @bugged87 很难说,因为这取决于这些函数的实际作用(我的意思是它们的目标是什么,而不是实现细节),而且我们现在只有示例存根。
  • 他们只是在尝试添加对使用 C# 方法链接的功能管道的支持。请参阅 Dave Fancher 的 post
  • @bugged87 对于您的问题中的这个特定方法,到目前为止没有想到任何事情(除非您从我上次的编辑中看到 - 您在 asyncAction 中实际执行的操作可能有助于编译器在没有调用者指定类型为st)。至于链接中的MapAsync - 这应该不是问题,因为TResult 有助于解决这个问题。
  • 正确,我只有TeeAsync 重载的问题。不过很好的答案。我不认为像你那样通过添加额外的上下文来测试推理引擎。现实世界的代码很可能会有这种上下文,歧义毕竟不会成为问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多