DEV Community

Cover image for Why we can't use Out or Ref parameters with Asynchronous method in .Net Core ?
Omar Zouaid
Omar Zouaid

Posted on

Why we can't use Out or Ref parameters with Asynchronous method in .Net Core ?

Do you ever find yourself in a situation where you need to return multiple values from a method?

The standard approach is whether to create a class object which hold all parameters as properties and use it as a return type, but this approach can be cumbersome and you will find yourself creating meaningless objects. Better approach is using dotnet Out parameters.

But it's not always possible to use out parameter, because in my case I was dealing with asynchronous method, and what I was unware of is the fact that the dotnet compiler forbid the use of out parameter inside asynchronous methods :

Error Text

And that pushed me to wonder why asynchronous method are designed on that way? and how can I figure out a workaround to this limitation?

Internal State machine (IAsyncStateMachine)

So to understand the problem we need to know how compiler deals with Asynchronous code.

So Every time we have an asynchronous method, the compiler actually turns the method into an internal state object.

Internal state class is designed in build time and instantiated in run time by the CLR, it implements the IAsyncStateMachine interface. This class is responsible for keeping the state of your method during the life cycle of the asynchronous operation, it encapsulates all the variables of your method as fields, splits your code into sections that are executed as the state machine transitions between states, so that the thread can leave the method and when it comes back the state is intact.

Common Language Runtime (CLR) is .NET run-time environment that runs the code and provides services that make the development process easier.

So let's create a class with asnychronous method and see how generated class will looks like. In the code below ExampleAsyncMethod is our async method :

    public class ExampleAsyncMethod
    {
        public async Task ExampleAsyncMethod( )
        {
            string variable = string.Empty;
            await CallApiAsync();
            Debug.WriteLine("First Await");
            variable = "new value";
            await CallApiAsync();
            Debug.WriteLine("Second Await");
            variable = "second new value";
        }
        public async Task CallApiAsync()
        {
            //Api call
        }
}
Enter fullscreen mode Exit fullscreen mode

When we try to build this code, .Net compiler will generate an internal state class :

[CompilerGenerated]
private sealed class <ExampleAsyncMethod>d_0 : IAsyncStateMachine
{
    //fields
    public int <>1_state;
    public ExampleAsync.ExampleAsync<>4_this;
    public AsyncTaskMethodBuilder<>t_builder;
    public TaskAwaiter<>u_1;
    public int <variable>5_1;
    // other fields
Enter fullscreen mode Exit fullscreen mode

Examining the generated class, we notice that our internal variable “variable” is now a class field.

We can also see other class fields used internally to maintain the state of the IAsyncStateMachine, one of them is MoveNext() which is a method with a big switch block. So basically MoveNext() we'll be called whenever there's state transitions (task completed) in our asynchronous code. The code below is a part of the generated MoveNext() method :

private void MoveNext()
{
    int num = this.<>1_state;
    try
    {
        TaskAwaiter awaiter;
        ExampleAsync.ExampleAsync.< ExampleAsyncMethod > d_0d_;
        switch (num)
        {
            case 0:
                break;
            case :
                goto Label_00D6;
            default:
                this.< myVariable > 5_1 = 0;
                awaiter = this.<>4_this_this.CallApiAsync().GetAwaiter();
                if(awaiter.get_IsCompleted())
                {
                    goto Label_007E;
                }
                this.<>1__state = num;
                this.<>u__1 = awaiter;
                d__ = this;
                return;
        }
        awiter.GetResult();
        Debug.WriteLine("First Await");
        this.<variable> 5_1 = 1;
        TaskAwaiter awaiter2 = this.<>4_this.CallApiAsync().GetAwaiter;
        if(awaiter2.get_IsCompleted())
        {
            goto Label_00F2;
        }
    }
    this.<> 1__state = num = 1;
    this.<> u__1 = awaiter2;
    d__ = this;
    return;
Label_00D6:
    awaiter2 = this.<> u_1;
    this.<>u_1 = new TaskAwaiter();
    this.<>1_state = num - 1;
Label_00F2:

    /// other code labels

}
Enter fullscreen mode Exit fullscreen mode

Back to our question

So we have seen how the compiler turn async method to state class, now let's go back on topic, Why we can't declare Out or Ref parameters in asynchronous method? So according to Lucian Wischik a member of the C# language design team, this problem is because of some limitation of CLR. Unfortunately in internal state class we can't store the address of an out or ref parameter, basically CLR has no safe way to deal with the address of an object. As consequence the compiler forbid the use of Ref and out parameters in asynchronous method.

Workaround

So till now I have explained why the CLR forbid the use of ref and out parameter in async method. But what can we do to overpass this limitation? My solution to this problem was using Tuples. Since the version 7, C# brought to table the notion of Tuples, an easy way to store multiple items in a single variable. So with tuples we can return mutuple values from method with no need to use out parameter.

So instead of using code like this (which is forbidden by the compiler):

public async Task<int> GetDataTaskAsync(out int parameters)
{
    //...
    return someIntValueTask;
}
Enter fullscreen mode Exit fullscreen mode

I switch to Tuples :

public async Task<Tuple<int, int>> GetDataTaskAsync()
{
    //...
    Tuple<int, int> result = await TaskReturnTuple();
    return result;
}
Enter fullscreen mode Exit fullscreen mode

In my case I just returned two values, but with Tuples we can return as many values as we want with no constraint.

I hope this article helped you to understand why we can't declare out and ref parameters in asynchronous method, if you have any question or comment please feel free to express your thoughts 😉.

Top comments (0)