(当然,要摆脱问题,简单地说Task.Run((Func<int>)MyIntReturningMethod)
。)
Task
这与等等完全无关。
这里要注意的一个问题是,当存在很多重载时,编译器错误文本将只关注一对“对”重载。所以这很令人困惑。原因是确定最佳重载的算法会考虑所有重载,并且当该算法得出无法找到最佳重载的结论时,它不会为错误文本产生特定的重载对,因为所有重载都可能(或可能不会)已经参与。
要了解会发生什么,请参阅以下简化版本:
static class Program
{
static void Main()
{
Run(() => 5); // compiles, goes to generic overload
Run(M); // won't compile!
}
static void Run(Action a)
{
}
static void Run<T>(Func<T> f)
{
}
static int M()
{
return 5;
}
}
正如我们所看到的,这绝对没有引用Task
,但仍然产生同样的问题。
请注意,匿名函数转换和方法组转换(仍然)不是完全相同的东西。详细信息可在C# 语言规范中找到。
拉姆达:
() => 5
实际上甚至不能转换为System.Action
类型。如果您尝试这样做:
Action myLittleVariable = () => 5;
它将失败并出现错误 CS0201:只有 assignment、call、increment、decrement、await 和 new object 表达式可以用作语句。所以很清楚与 lambda 一起使用哪个重载。
另一方面,方法组:
M
可转换为Func<int>
和Action
。请记住,完全允许不获取返回值,就像下面的语句:
M(); // don't use return value
本身是有效的。
这类回答了这个问题,但我会举一个额外的例子来说明一点。考虑这个例子:
static class Program
{
static void Main()
{
Run(() => int.Parse("5")); // compiles!
}
static void Run(Action a)
{
}
static void Run<T>(Func<T> f)
{
}
}
在最后一个示例中,lambda 实际上可以转换为两种委托类型!(只需尝试删除泛型重载。)对于 lambda 箭头的右侧=>
是一个表达式:
int.Parse("5")
这本身作为一个声明是有效的。但是在这种情况下,重载解析仍然可以找到更好的重载。正如我之前所说,检查 C# 规范。
受 HansPassant 和 BlueRaja-DannyPflughoeft 的启发,这是一个最终的(我认为)示例:
class Program
{
static void Main()
{
Run(M); // won't compile!
}
static void Run(Func<int> f)
{
}
static void Run(Func<FileStream> f)
{
}
static int M()
{
return 5;
}
}
请注意,在这种情况下,绝对int
5
不可能将 转换为System.IO.FileStream
. 方法组转换仍然失败。这可能与具有两个普通方法的事实有关int f();
,FileStream f();
例如,某些接口从两个不同的基接口继承,无法解析调用f();
。返回类型不是 C# 中方法签名的一部分。
我仍然避免Task
在我的回答中引入,因为它可能会给人关于这个问题的错误印象。人们很难理解Task
,而且它在 BCL 中相对较新。
这个答案已经发展了很多。最后,事实证明这与线程Why is Func<T>
ambiguous with中的根本问题相同Func<IEnumerable<T>>
?. 我的例子Func<int>
和Func<FileStream>
几乎一样清楚。Eric Lippert 在其他线程中给出了一个很好的答案。