c#.net为什么Task.Run似乎以不同于其他代码的方式处理Func <T>?

Mic*_*ett 18 c# asynchronous tap

作为.NET 4.5一部分的新Task.Run静态方法似乎没有像人们期望的那样运行.

例如:

Task<Int32> t = Task.Run(()=>5);     
Run Code Online (Sandbox Code Playgroud)

编译好,但是

Task<Int32> t = Task.Run(MyIntReturningMethod);
...
public Int32 MyIntReturningMethod() {
  return (5);
  }
Run Code Online (Sandbox Code Playgroud)

抱怨MyIntReturningMethod返回了错误的类型.

也许我只是不了解正在调用Task.Run的哪个重载.但在我看来,我上面的lambda代码看起来很像Func<Int32>,而且MyIntReturningMethod绝对兼容Func<Int32>

对于发生了什么的任何想法?迈克尔

Jep*_*sen 11

(当然,要解决问题,简单地说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;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,这绝对没有参考Task,但仍然会产生同样的问题.

请注意,匿名函数转换和方法组转换(仍)不完全相同.详细信息可在C#语言规范中找到.

lambda:

() => 5
Run Code Online (Sandbox Code Playgroud)

实际上甚至不能转换为System.Action类型.如果您尝试这样做:

Action myLittleVariable = () => 5;
Run Code Online (Sandbox Code Playgroud)

它将失败并出现错误CS0201:只能将赋值,调用,递增,递减,等待和新对象表达式用作语句.因此很清楚与lambda一起使用哪个重载.

另一方面,方法组:

M
Run Code Online (Sandbox Code Playgroud)

是可转换为Func<int>Action.请记住,完全允许接收返回值,就像语句一样:

M(); // don't use return value
Run Code Online (Sandbox Code Playgroud)

本身是有效的.

这种方式回答了这个问题,但我会举一个额外的例子来说明一个问题.考虑这个例子:

static class Program
{
    static void Main()
    {
        Run(() => int.Parse("5"));  // compiles!
    }

    static void Run(Action a)
    {
    }
    static void Run<T>(Func<T> f)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

在最后一个示例中,lambda实际上可以转换为两种委托类型!(只是尝试删除泛型重载.)对于lambda箭头的右侧=>是一个表达式:

int.Parse("5")
Run Code Online (Sandbox Code Playgroud)

这本身就是一个有效的声明.但是在这种情况下,重载分辨率仍然可以找到更好的过载.正如我之前所说,检查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;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,在这种情况下,绝对没有办法int 5可以转换为System.IO.FileStream.方法组转换仍然失败.这可能与两个普通的方法其实int f();FileStream f();,例如通过从两个不同的基本接口部分接口继承,就没有办法解决了通话f();.返回类型不是C#中方法签名的一部分.

我仍然避免Task在我的回答中介绍,因为它可能会给出这个问题的错误印象.人们很难理解Task,而且它在BCL中相对较新.


这个答案已经发展了很多.最后,事实证明,这与线程中的底层问题真的相同为什么Func<T>不明确Func<IEnumerable<T>>.我的例子Func<int>Func<FileStream>几乎一样清楚.Eric Lippert在其他帖子中给出了一个很好的答案.


Han*_*ant 9

这应该在.Net 4.0中修复,但Task.Run()是.Net 4.5的新功能

通过添加Task.Run(Func<Task<T>>)方法,.NET 4.5有自己的重载歧义.并支持在C#版本5中的async/await.它允许隐式转换T foo()Func<Task<T>>.

这是async/await非常甜的语法糖,但在这里会产生空洞.在方法重载中没有考虑async方法声明中关键字的遗漏,这会打开另一个苦差事框,程序员忘记在他们意图使用异步时.否则遵循通常的C#约定,只考虑方法签名中的方法名称和参数进行方法重载选择.

需要显式使用委托类型来解决歧义.

  • @Jeppe和Hans:Jeppe是正确的,只有`Run(Action a)`和`Run <T>(Func <T> f)`签名,编译器会发出相同的错误.如果用`Run <T>(Func <Task <T >> f)`替换`Run(Action a)`,则不再出现错误.**但是**,如果用`Run(Func <Task <int >> f)`替换它,模糊方法错误就会返回!这可以是通用的,如[here](http://hastebin.com/wewuxenice.vala).编译器*确实*实际上将所有三个`Task.Run()`重载显示为模糊的重载,所以事实证明你是对的:) (2认同)

Meh*_*dad 1

看起来像是一个重载解决问题。编译器无法判断您正在调用哪个重载(因为首先它必须找到要创建的正确委托,但它不知道该委托,因为这取决于您正在调用的重载)。它必须猜测并检查,但我猜它不是那么聪明。

  • 不确定这是一个有用的观察,但是当我阅读 Eric 的关于编译器如何推断这一点并推断出这一点的帖子时,我想知道:普通程序员在编写代码时应该如何记住所有这些细节?至少对我来说,编写委托/匿名/通用代码已经变得相当不确定。如果我从编译器中得到语法错误,我通常甚至不会尝试找出原因(通常是因为我不能)。相反,我只是继续尝试我认为“等效”的语法,直到它起作用,或者我一起放弃。对于 c# 来说不是一个好的注释 (3认同)