【问题标题】:C# Call is Ambiguous when passing a method group as delegate将方法组作为委托传递时,C# 调用不明确
【发布时间】:2015-08-13 17:29:13
【问题描述】:

希望有人可以向我解释这一点。抱歉,如果它是重复的,解释我所看到的内容的关键字现在超出了我的范围..

这是一些编译的代码

class Program
{
    static void Main(string[] args)
    {
        new Transformer<double, double>(Math.Sqrt);
    }
}

class Transformer<Tin, Tout>
{
    Func<Tin, Task<Tout>> actor;
    public Transformer(Func<Tin, Tout> actor)
    {
        this.actor = input => Task.Run<Tout>(() => actor(input));
    }
}

这是一些没有的代码

class Program
{
    static void Main(string[] args)
    {
        new Transformer<double, double>(Math.Sqrt);
    }
}

public class Transformer<Tin, Tout>
{
    Func<Tin, Task<Tout>> actor;
    public Transformer(Func<Tin, Tout> actor)
    {
        this.actor = input => Task.Run<Tout>(() => actor(input));
    }

    public Transformer(Func<Tin, Task<Tout>> actor)
    {
        this.actor = actor;
    }
}

通过添加构造函数重载,这显然会产生歧义,但我不确定为什么。 Math.Sqrt 没有重载,显然返回类型为 double,而不是 Task

这是错误:

以下方法或属性之间的调用不明确:'ConsoleApplication1.Transformer.Transformer(System.Func)' 和 'ConsoleApplication1.Transformer.Transformer(System .Func>)'

有人可以解释为什么编译器的选择不明显吗?


为关心的人提供简单的解决方法:

class Program
{
    static void Main(string[] args)
    {
        new Transformer<double, double>(d => Math.Sqrt(d));
    }
}

【问题讨论】:

  • 顺便说一句,还有另一种解决方法:new Transformer&lt;double, double&gt;((Func&lt;double, double&gt;) Math.Sqrt);
  • 可能是一个错误。我自己已经几次达到类型推断的极限,并且看到它与此类似地失败。我会告诉你什么是真正可怕的——你没有将你的构造函数调用链接在一起。啧啧啧。 public Transformer(Func&lt;Tin, Tout&gt; actor) : this(input =&gt; Task.Run&lt;Tout&gt;(() =&gt; actor(input)))。怀疑这会解决,但它会防止不幸的错误!
  • @Will 公平评论虽然这是(显然,我希望)一个简单的复制。我实际上在 Microsoft Dataflow(msdn.microsoft.com/en-us/library/hh228603(v=vs.110).aspx) 类上看到了这种行为:TransformBlock etc...
  • 参见this answer to the same issue 了解编译器需要显式键入委托的简单说明(如@Szer 建议的那样)。

标签: c#


【解决方案1】:

您对Func&lt;Tin, Tout&gt; 的工作方式略有误解。看看the docs

public delegate TResult Func<in T, out TResult>(
    T arg
)

第一个参数是参数,最后一个参数是返回类型。

当您查看此代码的简化版本时:

internal class Program
{
    public static void Main(string[] args)
    {
        new MyClass<double, double>(Method);
    }

    private static double Method(double d)
    {
        throw new NotImplementedException();
    }
}


internal class MyClass<T, U>
{
    public MyClass(Func<U, T> arg)
    {
    }

    public MyClass(Func<U, Task<T>> arg)
    {
    }
}

您会注意到两个参数首先指定 double,这是一个参数,然后返回类型不同:TTask&lt;T&gt;

然而,我们都知道:重载是基于方法名称、参数数量和参数类型完成的。返回类型完全被忽略。在我们的例子中,这意味着我们有两个Func&lt;Tin, Tout&gt;double 作为参数,T vs Task&lt;T&gt; 作为返回类型。

切换参数编译就好了:

internal class MyClass<T, U>
{
    public MyClass(Func<T, U> arg)
    {
    }

    public MyClass(Func<Task<T>, U> arg)
    {
    }
}

如果您查看 Visual Studio,您会注意到此方法现在显示为灰色,这是有道理的,因为 Method 的参数类型为 double,因此将始终匹配 T 而不是Task&lt;T&gt;.

因此,如果您传入不同的异步方法,为了测试它现在是否会达到正确的重载,您可以添加第二个方法:

private static double MethodAsync(Task<double> d)
{
   throw new NotImplementedException();
}

并使用

调用它
new MyClass<double, double>(MethodAsync);

您现在会注意到异步 Func&lt;Task&lt;T&gt;, U&gt;&gt; 被命中(您可以通过简单地从构造函数打印到控制台来验证)。


简而言之:您正在尝试对返回类型执行重载解析,这显然是不可能的。

【讨论】:

  • 嗯..我仍然没有得到任何东西。歧义在于具有不同参数的构造函数。 Math.Sqrt 没有歧义——只有一个选项。根据您的回答,这不应该导致正确的分辨率吗?
  • @eisenpony:歧义不在 Math.Sqrt 中——它在构造函数的 Func 参数中。您说它们采用不同的参数,但它们没有:它们接受不同的返回类型,但参数相同。在这种情况下,构造函数似乎采用不同的参数,但您必须更深入地研究。首先,它匹配一个 Func 和一个 double 并找到它。然后它在 Func 内部查看它必须使用哪一个,并找到 2 个具有相同参数但返回类型不同的 Func,因此它被卡在那里。
  • 好的,我知道了。我并不是建议 Func 采用不同的参数,我认为构造函数采用不同的参数。但是,从编译器的 POV 来看,您所说的 Func 存在彼此无法区分是有道理的。这是编译器的正常行为,因为当您正常调用方法时,无法预期预期的返回类型。但是,在这种情况下,感觉编译器可以将返回类型用作其解析的一部分,因为 Math.Sqrt.method 组中只有一种可能的返回类型:double。
猜你喜欢
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 2011-05-29
  • 2023-03-25
  • 1970-01-01
  • 2010-10-25
  • 1970-01-01
  • 2022-08-24
相关资源
最近更新 更多