103

Does anyone know why async methods are not allowed to have ref and out arguments? I've done a bit of research on it but the only thing I could find was that it has to do with the stack unrolling.

Pang
  • 9,564
  • 146
  • 81
  • 122
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • I think that you can find the answer here http://stackoverflow.com/questions/18716928/how-to-write-a-async-method-with-out-parameter – Alessio Jan 01 '14 at 11:25
  • Take a look at servy42's answer from http://social.msdn.microsoft.com/Forums/vstudio/en-US/df2b3fb6-000b-438f-a32d-a0504ee66e77/whats-the-reason-that-async-methods-cant-have-outref-parameters – Soner Gönül Jan 01 '14 at 11:26
  • http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have-ref-or-out-parameters?forum=async – santosh singh Jan 01 '14 at 11:27
  • Related: [How to write an async method with out parameter?](https://stackoverflow.com/questions/18716928/how-to-write-a-async-method-with-out-parameter) – Theodor Zoulias Apr 07 '21 at 11:38

2 Answers2

153

Does anyone know why async methods are not allowed to have ref and out arguments?

Sure. Think about it - an async method usually returns almost immediately, long before most of the actual logic is executed... that's done asynchronously. So any out parameters would have to be assigned before the first await expression, and there'd quite possibly have to be some restriction on ref parameters to stop them from being used after the first await expression anyway, as after that they may not even be valid.

Consider calling an async method with out and ref parameters, using local variables for the arguments:

int x;
int y = 10;
FooAsync(out x, ref y);

After FooAsync returns, the method itself could return - so those local variables would no longer logically exist... but the async method would still effectively be able to use them in its continuations. Big problems. The compiler could create a new class to capture the variable in the same way that it does for lambda expressions, but that would cause other issues... aside from anything else, you could have a local variable changing at arbitrary points through a method, when continuations run on a different thread. Odd to say the least.

Basically, it doesn't make sense to use out and ref parameters for async methods, due to the timing involved. Use a return type which includes all of the data you're interested in instead.

If you're only interested in the out and ref parameters changing before the first await expression, you can always split the method in two:

public Task<string> FooAsync(out int x, ref int y)
{
    // Assign a value to x here, maybe change y
    return FooAsyncImpl(x, y);
}

private async Task<string> FooAsyncImpl(int x, int y) // Not ref or out!
{
}

EDIT: It would be feasible to have out parameters using Task<T> and assign the value directly within the method just like return values. It would be a bit odd though, and it wouldn't work for ref parameters.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    @hvd: Yes, it could potentially do that - will edit to include that as a possibility. Although it couldn't do that to pass one `out` parameter as an argument for another `out` parameter... – Jon Skeet Jan 01 '14 at 11:32
  • Sure, that's a good point. It could pass the reference passed in, but if *that* refers to a local variable not wrapped in a class, you still have the same problem. –  Jan 01 '14 at 11:38
  • I think I was mistaken, and that it's just plain impossible without extensions to CIL. –  Jan 01 '14 at 11:53
  • I don't get it, did you mean `Action` instead of `Task`? If so, nice idea. –  Jan 01 '14 at 12:04
  • @hvd: No, I mean Task. Think of it as just another return value - the task wouldn't complete until the whole method had completed, at which point the value would be the last one assigned in the method. Bit odd, but feasible. – Jon Skeet Jan 01 '14 at 12:06
  • Ah, I thought you meant what would effectively be `await FooAsyncImpl(val => x = val, val => y = val)` instead of `await FooAsyncImpl(out x, out y)`, which would also work for `out` and only for `out` (and you don't read it after setting it). Thanks for the clarification, I'm still not sure I get it, but I'll think about it. :) –  Jan 01 '14 at 12:12
30

C# is compiled to CIL, and CIL does not support this.

CIL does not have async natively. async methods are compiled to a class, and all (used) parameters and local variables are stored in class fields, so that when a specific method of that class is called, the code knows where to continue executing and what values the variables have.

ref and out parameters are implemented using managed pointers, and class fields of managed pointer type are not allowed, so the compiler cannot preserve the reference passed in.

This restriction on managed pointers in class fields prevents some nonsensical code, as explained in Jon Skeet's answer, as a managed pointer in a class field might refer to a local variable of a function that has already returned. However, this restriction is so strict that even safe and otherwise correct usage is rejected. The ref/out fields could work, if they referred to another class field, and the compiler made sure to always wrap local variables passed with ref/out in a class (like it already knows how to do).

So, C# simply does not have any way to work around the restrictions imposed by CIL. Even if the C# designers want to allow it (I'm not saying they do), they cannot.