11

A previous question made me wonder why the following method would raise a compile time error:

The return type of an async method must be void, Task or Task

public async T MyMethodAsync<T>() where T : Task
{
     // Irrelevant code here which returns a Task
}

Since we know at compile time that T is always a Task or a derived type, why won't this work?

Edit

The reason I'm asking is that a method may return a Task or a Task<T>. Let's say the method can return either and I don't want to duplicate code.

Of course this is all theoretical and isn't ment for production purposes.

Edit 2

Found a great article by Lucian Wischik: Why must async return Task

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Did you mean to make that a generic method? – Jon Skeet May 29 '14 at 17:35
  • Did you miss the generic definition for the function? It should be `MyMethodAsync()`. Also are you returning a `T` or a `Task`? If you return the later you can just make the return type `Task`. – Guvante May 29 '14 at 17:35
  • I'm not sure if there's a reason for the compiler to refuse this, but stepping back I have to wonder why a generic with `where T : AnySpecificType` needs to be generic in the first place. – David May 29 '14 at 17:37
  • The return type might be a `Task` for example – Yuval Itzchakov May 29 '14 at 17:38
  • 2
    @YuvalItzchakov: Please try to write a method body which would be valid for `T=Task` and also for `T=Task`. – Jon Skeet May 29 '14 at 17:39
  • Async methods lift a regular type into a task type, and the return type of the method is `Task` or `Task` depending on whether a value is returned. The compiler doesn't have some general way of creating values some arbitrary derived type `T`. – Lee May 29 '14 at 17:39
  • You cannot do both `Task` and `Task` in one method, you will need to write two methods, although there are several ways to interop between the two (the most common being a `Task` that returns `null`) – Guvante May 29 '14 at 17:41
  • Shot in the dark, did you already use the name Task in one of your own classes? You can always specify the name in a using directive if that is the case. `using AsyncTask = System.Threading.Tasks.Task;`. – Travis J May 29 '14 at 17:41

2 Answers2

14

Three problems:

  • Just because T is "Task or a derived type" doesn't mean that it's Task or Task<T>. What would you expect if I called MyMethodAsync<MyCustomTask> where MyCustomTask derives from Task?

  • The compiler needs to know whether it's building a state machine returning Task or Task<T> when it compiles the method - it uses different helper classes in the different cases

  • If an async method has a return type of Task, any return statements can't specify a value; if it has a return type of Task<T> any return statements must specify a value which is implicitly convertible to T. How can that work within MyMethodAsync? It's a bit like saying "my method is either void or returns a T - you can decide when you call it".

It's not clear what you're trying to achieve here, but basically this isn't going to work.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I see. So basically this has to do with the compiler needing to know if there isn't a return value (if its a `Task`) or if there is (`Task`). As for `MyCustomTask`, is a derived type of `Task` applicable as a return type of an `async` method? – Yuval Itzchakov May 29 '14 at 17:49
  • @YuvalItzchakov: No, it's not - but it *would* meet your generic type parameter constraint. – Jon Skeet May 29 '14 at 17:49
  • May I ask why? Or would that be a different question? – Yuval Itzchakov May 29 '14 at 17:52
  • @YuvalItzchakov: Why what? Why it's not a valid return type? How would you expect the compiler to know how to construct one? Bear in mind that the compiler turns the async method into a state machine, and works out how to return a `Task` or `Task` if necessary. It does that via helper classes that are specific to `Task` or `Task`. – Jon Skeet May 29 '14 at 17:52
  • 1
    Right, now I get it. I wasn't aware of the problem of inferring an unknown derived task type. I should explore those helper classes to understand exactly what happeneds. – Yuval Itzchakov May 29 '14 at 17:57
  • The biggest problem is the fact that the compiler introduces code that creates an instance of the return type, not the user. For `Task` and `Task`, it knows how to do that. If the return type of the method were `MyCustomTask`, then even if `MyCustomTask` was derived from `Task` the compiler would not know the correct way to create an instance of that type to return. – Sam Harwell May 29 '14 at 18:20
  • Found a great article about that. http://blogs.msdn.com/b/lucian/archive/2012/11/22/why-must-async-methods-return-task.aspx – Yuval Itzchakov May 29 '14 at 18:29
-1

I cannot think of a valid definition of MyMethodAsync that would allow it to return a generic T derived from Task without knowing at compile time what that type is or taking a parameter of some kind.

If you are really returning a Task or Task<T> then you can update your signature to reflect that fact and avoid the problem.

If you honestly need some type derived from Task then you will need to rewrite your logic to instead return Task or Task<T> and wrap around that other type. Assuming that is unacceptable you will need to drop the async and handle the state machine yourself.

Guvante
  • 18,775
  • 1
  • 33
  • 64