18

The new Task.Run static method that's part of .NET 4.5 doesn't seem to behave as one might expect.

For example:

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

compiles fine, but

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

complains that MyIntReturningMethod is returning the wrong type.

Perhaps I am just not understanding which overload of Task.Run is being called. But in my mind, my lambda code above looks a lot like a Func<Int32>, and MyIntReturningMethod is definitely compatible with Func<Int32>

Any ideas of what's going on? Michael

Michael Ray Lovett
  • 6,668
  • 7
  • 27
  • 36
  • Adding a bounty, I'd like to know why this doesn't work as well. This was [supposed to be fixed in .Net 4.0](http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx), but `Task.Run()` is new to .Net 4.5... – BlueRaja - Danny Pflughoeft Nov 08 '13 at 21:36

6 Answers6

11

(Of course, to get out of the problem, simply say Task.Run((Func<int>)MyIntReturningMethod).)

This has absolutely nothing to do with Task and so on.

One problem to be aware of here is that when very many overloads are present, the compiler error text will focus on just one "pair" of overloads. So that is confusing. The reason is that the algorithm to determine the best overload considers all overloads, and when that algorithm concludes that no best overload can be found, that does not produce a certain pair of overloads for the error text because all overloads may (or may not) have been involved.

To understand what happens, see instead this simplified version:

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

As we see, this has absolutely no reference to Task, but still produces the same problem.

Note that anonymous function conversions and method group conversions are (still) not the exact same thing. Details are to be found in the C# Language Specification.

The lambda:

() => 5

is actually not even convertible to the System.Action type. If you try to do:

Action myLittleVariable = () => 5;

it will fail with error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement. So it is really clear which overload to use with the lambda.

On the other hand, the method group:

M

is convertible to both Func<int> and Action. Remember that it is perfectly allowed to not pick up a return value, just like the statement:

M(); // don't use return value

is valid by itself.

This sort-of answers the question but I will give an extra example to make an additional point. Consider the example:

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

    static void Run(Action a)
    {
    }
    static void Run<T>(Func<T> f)
    {
    }
}

In this last example, the lambda is actually convertible to both delegate types! (Just try to remove the generic overload.) For the right-hand-side of the lambda arrow => is an expression:

int.Parse("5")

which is valid as a statement by itself. But overload resolution can still find a better overload in this case. As I said earlier, check the C# Spec.


Inspired by HansPassant and BlueRaja-DannyPflughoeft, here is one final (I think) example:

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

Note that in this case, there is absolutely no way the int 5 could be converted into a System.IO.FileStream. Still the method group conversion fails. This might be related to the fact the with two ordinary methods int f(); and FileStream f();, for example inherited by some interface from two different base interfaces, there is no way to resolve the call f();. The return type is not part of a method's signature in C#.

I still avoid to introduce Task in my answer since it could give a wrong impression of what this problem is about. People have a hard time understanding Task, and it is relatively new in the BCL.


This answer has evolved a lot. In the end, it turns out that this is really the same underlying problem as in the thread Why is Func<T> ambiguous with Func<IEnumerable<T>>?. My example with Func<int> and Func<FileStream> is almost as clear. Eric Lippert gives a good answer in that other thread.

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • I might be missing something, but if the method group being convertible to both delegates throws an error, shouldn't the lambda in the last example also produce an error? How does the compiler resolve the overload in the final example? – mao47 Nov 15 '13 at 21:30
  • Well, add `static Task Run(Func> f) { return null; }` to reproduce the error message. That your own version of Run is ambiguous as well does not otherwise say anything about the ambiguity of the Task class methods. The `Task.Run(Func)` method is also ambiguous, the C# compiler just doesn't give the complete list. – Hans Passant Nov 15 '13 at 21:31
  • 1
    @mao47 Good question. That's why I added the last example. To answer that, I would have to read the C# Language Specification carefully. There are separate rules for method group conversions and lambda conversions. – Jeppe Stig Nielsen Nov 15 '13 at 21:35
  • @HansPassant No, that is incorrect. If in my first `Program` you insert your `Run` overload (of your comment) ***and*** remove all other overloads of `Run`, such that there is only one `Run` overload, then you will find that "your" overload is not even "applicable". It can never be it! See my comment to your answer. – Jeppe Stig Nielsen Nov 15 '13 at 21:39
  • Re: edit: *"The return type is not part of a method's signature in C#"* - If that's truly the reason, then why doesn't it compile when `Run(Func)` is the only signature? – BlueRaja - Danny Pflughoeft Nov 15 '13 at 22:03
  • @BlueRaja-DannyPflughoeft How would that compile? The `Run` method could use the returned value from `f()` as a `FileStrem`, but that value is the integer `5`. – Jeppe Stig Nielsen Nov 15 '13 at 22:10
  • @BlueRaja-DannyPflughoeft Try these three interface types: `interface IBase1 { int f(); } interface IBase2 { FileStream f(); } interface IDerived : IBase1, IBase2 { }` Then inside some class, make a method: `static void MyPoint(IDerived daThing) { int x = daThing.f(); }` Do you expect that to compile? No, the `f` is ambiguous. Remove the `int` version of `f` to leave only the `FileStream` version there. Will it compile now? No, even if it is clear what `f` to use (unique overload), a `FileStream` is not an `int`. – Jeppe Stig Nielsen Nov 15 '13 at 22:14
  • @Jeppe: I am obviously aware of that. But, that is exactly my point - if the compiler can already determine the return-type from the signature, then how can you say *"The return type is not part of a method's signature"* ? – BlueRaja - Danny Pflughoeft Nov 15 '13 at 22:23
  • @BlueRaja-DannyPflughoeft Maybe I am confusing "method overload resolution" (which method does he call?) and "method group conversion" (which delegate type is he creating from the method name?) a bit too much here. The phrase _"The return type is not part of a method's signature"_ is appropriate for the situation of my latest comment. Even if they can see that choosing one (out of two possible) `f` will go wrong, they still call out the ambiguity. Because overload resolution is like that, it is (perhaps) _natural_ that method group conversion is similar. It is an analogy, not the same. – Jeppe Stig Nielsen Nov 15 '13 at 22:34
  • ... So `Run(M)` won't choose between `Func` and `Func` because both have the same argument list, namely `()`. Ambiguity. If it chose one of them, it would _later_ run into a problem with the return type. If it chose the other one, it would _later_ be happy with the return type, and all would go well. But it won't choose between the two. Apparently. (Same with choosing between `Action` and `Func`; both have parameter list `()`.) We get no further until we both read the C# Spec. – Jeppe Stig Nielsen Nov 15 '13 at 22:37
9

This was supposed to be fixed in .Net 4.0, but Task.Run() is new to .Net 4.5

.NET 4.5 has its own overload ambiguity by adding the Task.Run(Func<Task<T>>) method. And the support for async/await in C# version 5. Which permits an implicit conversion from T foo() to Func<Task<T>>.

That's syntax sugar that's pretty sweet for async/await but produces cavities here. The omission of the async keyword on the method declaration is not considered in the method overload selection, that opens another pandora box of misery with programmers forgetting to use async when they meant to. Otherwise follows the usual C# convention that only the method name and arguments in the method signature is considered for method overload selection.

Using the delegate type explicitly is required to resolve the ambiguity.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    This answer is **wrong**. The overload `Task.Run(Func>)` mentioned does not participate in the overload resolution problem (see my answer). The claim that an implicit conversion from `T foo()` to `Func>` exists is also wrong. – Jeppe Stig Nielsen Nov 15 '13 at 20:59
  • Ouch, you are declaring a bug in the compiler. Strong claim. – Hans Passant Nov 15 '13 at 21:24
  • 1
    Am I? I don't think I am. I just say that the error message does not necessarily show the two most relevant overloads when overload resolution is required to _not_ produce a best overload. – Jeppe Stig Nielsen Nov 15 '13 at 21:29
  • 1
    Correct, it only shows two even if there are more ambiguous overloads. Two are enough. Your claim that the `Func>` overload does not participate is a bug claim. – Hans Passant Nov 15 '13 at 21:34
  • 2
    @Jeppe and Hans: Jeppe is correct that, with just `Run(Action a)` and `Run(Func f)` signatures, the compiler will emit the same error. If you replace the `Run(Action a)` with a `Run(Func> f)`, the error no longer occurs. **However**, if you replace it with a `Run(Func> f)`, the ambiguous method error returns! This can be made generic, as in [here](http://hastebin.com/wewuxenice.vala). The compiler *does* actually show all three `Task.Run()` overloads as ambiguous overloads, so it turns out you're both right :) – BlueRaja - Danny Pflughoeft Nov 15 '13 at 21:36
  • Er, to be precise, the ambiguous overload the compiler complains about is `Task.Run(Func)`, not `Task.Run(Func>)`. The latter does not cause an ambiguous method error; you can replicate this by tweaking the code in the link I posted above. – BlueRaja - Danny Pflughoeft Nov 15 '13 at 21:44
  • @BlueRaja-DannyPflughoeft That is interesting. If, in the code you linked, you remove ***all but one*** overload, leaving only the overload `public static void Run(Func> f)`, it will not compile with `Run(M)`. That is because Passant was wrong when he claimed there existed a conversion from `T foo()` to `Func>`. ***EDIT:*** We conclude that with method-group conversion an overload which is really "impossible" because of return type, can participate and "ruin" the game. – Jeppe Stig Nielsen Nov 15 '13 at 21:48
  • @Jeppe Hah, that is interesting! The same goes for `Run(Func>)` and `Run(Func)`. That is, if both `Run(Func)` and `Run(Func)` are defined, the compiler will error with an ambiguous call error, but if only `Run(Func)` is defined, the compiler will **still** error with an incorrect signature! – BlueRaja - Danny Pflughoeft Nov 15 '13 at 21:53
  • Hans Passant was not so wrong after all. My apologies. The overload `Task.Run(Func>)` from the compiler error text is more relevant than I thougt initially. Since a `Func>` represents a method with zero parameters, and the `MyIntReturningMethod` also has zero parameters, even if the return types are wildly different (and I still maintain that no conversion exists between `int` and `Task`!), this error pops up. My answer is edited and expanded several times. – Jeppe Stig Nielsen Nov 15 '13 at 23:35
  • This answer got us on the right track, but I have to award the bounty to @Jeppe, since he correctly noted that the same errors still occur (or don't occur) if you replace `Task` with `FileStream`, so it's not an issue with implicit conversation to `Task`. By the way Hans, congrats on the big 4-0-0! – BlueRaja - Danny Pflughoeft Nov 16 '13 at 02:16
3

When you pass a Func<TResult> into a method Run<TResult>(Func<TResult>) you don't have to specify the generic on the methodcall because it can infer it. Your lambda does that inference.

However, your function is not actually a Func<TResult> whereas the lambda was.

If you do Func<Int32> f = MyIntReturningMethod it works. Now if you specify Task.Run<Int32>(MyIntReturningMethod) you would expect it to work also. However it can't decide if it should resolve the Func<Task<TResult>> overload or the Func<TResult> overload, and that doesn't make much sense because its obvious that the method is not returning a task.

If you compile something simple like follows:

void Main()
{
    Thing(MyIntReturningMethod);
}


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

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

the IL looks like this....

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

(Some of the extra stuff is from LINQ Pad's additions... like the UserQuery part)

The IL looks identical as if you do an explicit cast. So it seems like the compiler does't actually know which method to use. So it doesn't know what cast to create automatically.

You can just use Task.Run<Int32>((Func<Int32>)MyIntReturningMethod) to help it out a bit. Though I do agree that this seems like something the compiler should be able to handle. Because Func<Task<Int32>> is not the same as Func<Int32>, so it doesn't make sense that they would confuse the compiler.

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
LameCoder
  • 1,287
  • 7
  • 22
1

Seems like an overload resolution problem. The compiler can't tell which overload you're calling (because first it has to find the correct delegate to create, which it doesn't know because that depends on the overload you're calling). It would have to guess-and-check but I'm guessing it's not that smart.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • That's correct. The compiler doesn't know if MyIntReturningMethod is Func or Action. – Sam Jul 27 '12 at 22:45
  • 1
    Well that really surprises me. The function clearly has an int32 return value, how could the compiler think it was an action? Also, passing in the function, which has a stated return type, seems much more clear than my lambda which the compiler has to infer the return type from the assignment into the task... Any enlightenment would be appreciated – Michael Ray Lovett Jul 28 '12 at 02:21
  • MyIntReturningMethod is not a method group with multiple methods. There is no ambiguity here. I don't think this answer is correct. – usr Jul 28 '12 at 18:06
  • @usr: Sorry I should've been more clear. I just provided that link as an example of where the compiler could reasonably be expected to do the inference, but can't. I agree the particular issue is not the same, though. – user541686 Jul 28 '12 at 18:07
  • 1
    @Mehrdad, the issue is not the same and it was (allegedly) resolved in C# 4.0. I don't think this issue applies here. – usr Jul 28 '12 at 18:08
  • 3
    Not sure this is a useful observation, but when I read postings by Eric about how the compiler infers this and deduces that, I wonder: How is that average programmer supposed to keep all those details in mind at the point of writing code? Anymore, writing delegate/anonymous/generic code has gotten to be, at least for me, quite non-deterministic. If I get syntax errors from the compiler I often don't even try to figure out why (usually because I can't). Instead, I just keep trying what I think is "equivalent" syntax, until it works, or I give up all together. Not a good note for c# – Michael Ray Lovett Jul 29 '12 at 22:32
  • 1
    "How is that average programmer supposed to keep all those details in mind at the point of writing code?" => He relies on ReSharper to alert him when he screws up. – Mike Strobel Nov 08 '13 at 22:51
0

The approach of Tyler Jensen works for me.

Also, you can try this using a lambda expression:

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

    public int MyIntReturningMethod()
    {
        return (5);
    }
}
Community
  • 1
  • 1
Pliyo
  • 621
  • 2
  • 8
  • 14
-1

Here's my stab at it:

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);
    }
}
Tyler Jensen
  • 795
  • 4
  • 9