18

作为 .NET 4.5 一部分的新 Task.Run 静态方法的行为似乎与预期不同。

例如:

Task<Int32> t = Task.Run(()=>5);     

编译得很好,但是

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

抱怨 MyIntReturningMethod 返回错误的类型。

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

有什么想法吗?迈克尔

4

6 回答 6

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;
    }
}

正如我们所看到的,这绝对没有引用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 在其他线程中给出了一个很好的答案。

于 2013-11-15T20:53:41.120 回答
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# 约定,即仅将方法签名中的方法名称和参数考虑用于方法重载选择。

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

于 2013-11-08T23:38:57.537 回答
3

当您将 a 传递给Func<TResult>方法Run<TResult>(Func<TResult>)时,您不必在方法调用上指定泛型,因为它可以推断它。您的 lambda 会进行该推断。

但是,您的函数实际上不是 aFunc<TResult>而 lambda 是。

如果你这样做Func<Int32> f = MyIntReturningMethod,它会起作用。现在,如果您指定Task.Run<Int32>(MyIntReturningMethod),您希望它也可以工作。但是它无法决定是否应该解决Func<Task<TResult>>重载或Func<TResult>重载,这没有多大意义,因为很明显该方法没有返回任务。

如果你编译如下简单的东西:

void Main()
{
    Thing(MyIntReturningMethod);
}


public void Thing<T>(Func<T> o)
{
    o();
}

public Int32 MyIntReturningMethod()
{
return (5);
}

IL看起来像这样....

IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldftn       UserQuery.MyIntReturningMethod
IL_0009:  newobj      System.Func<System.Int32>..ctor
IL_000E:  call        UserQuery.Thing

(一些额外的东西来自 LINQ Pad 的添加......比如 UserQuery 部分)

IL 看起来与您进行显式转换一样。所以看起来编译器实际上并不知道使用哪种方法。所以它不知道要自动创建什么演员表。

你可以用Task.Run<Int32>((Func<Int32>)MyIntReturningMethod)它来帮助它一点。尽管我确实同意这似乎是编译器应该能够处理的事情。因为Func<Task<Int32>>与 不一样Func<Int32>,所以它们会混淆编译器是没有意义的。

于 2013-11-08T22:45:06.483 回答
1

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

于 2012-07-27T21:31:24.230 回答
0

Tyler Jensen的方法对我有用。

此外,您可以使用 lambda 表达式进行尝试:

public class MyTest
{
    public void RunTest()
    {
        Task<Int32> t = Task.Run<Int32>(() => MyIntReturningMethod());
        t.Wait();
        Console.WriteLine(t.Result);
    }

    public int MyIntReturningMethod()
    {
        return (5);
    }
}
于 2013-11-15T14:38:29.343 回答
-1

这是我的尝试:

public class MyTest
{
    public void RunTest()
    {
        Task<Int32> t = Task.Run<Int32>(new Func<int>(MyIntReturningMethod));
        t.Wait();
        Console.WriteLine(t.Result);
    }

    public int MyIntReturningMethod()
    {
        return (5);
    }
}
于 2013-11-14T21:36:55.730 回答