C# 7.0 – Generalized async return types 1


Facebooktwittergoogle_pluslinkedinmail

Before C# 7 every async  method had to return TaskTask<T> or void. The new C# version extends the range of returned types.

Note: You can view all code examples on GitHub. My environment: Visual Studio 2017 and .NET Framework 4.6.1.

The void-returning methods are recommended only for  async event handlers. It’s because they are fire and forget methods – it means that we don’t care what will be the result of an event.

The recommended return type for any other scenario is either Task – if a method doesn’t return a value or Task<T> – when a method returns a value.

ValueTask

There is a new predefined type added to C# 7.0  that can be returned by  async methods – ValueTask and ValueTask<T>.

    class Example1
    {
        async ValueTask MethodWithoutReturnValue()
        {
            await Task.Delay(1);
        }

        async ValueTask<int> MethodReturningValue()
        {
            await Task.Delay(1);
            return 1;
        }
    }

Note: To use the ValueTask one have to install System.Threading.Tasks.Extensions NuGet package.

There is a major difference between Task and ValueTask. The Task is a reference type and requires heap allocation. The ValueTask is a value type and is returned by value – meaning, no heap allocation. It’s recommended to use ValueTask when there is a high probability that a method won’t have to wait for async operations. For example, if a method returns cached or predefined results. This can significantly reduce the number of allocations and result in big performance improvement.

    class Example2
    {
        public async ValueTask<string> GetNameAsync(int id)
        {
            if (TryGetNameFromCache(id, out var name))
            {
                return name;
            }

            name = await GetNameFromDatabaseAsync(id);
            AddToCache(id, name);
            return name;
        }

        /* Other methods */
    }

There is one downside of using ValueTask – it’s not compatible with Task. If there is a method returning Task and it calls a method returning ValueTask, there is no way to convert ValueTask to Task.

    class Example3
    {
        Task<int> GetValueWithTask() => Task.FromResult(0);

        ValueTask<int> GetValueWithValueTask() => new ValueTask<int>(0);

        ValueTask<int> GetValue()
        {
            // This won't compile
            // CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task<int>' to 'System.Threading.Tasks.ValueTask<int>'

            // return GetValueWithTask();

            return GetValueWithValueTask();
        }

        async ValueTask<int> GetValueAsync()
        {
            // But this will work fine
            return await GetValueWithTask();
        }
    }

Custom async return types

It’s also allowed to define custom task-like types. It’s not easy to do it correctly and it’s not expected to be done often.

Here is the link to the roslyn documentation about how to build custom async task types.

 

Facebooktwittergoogle_pluslinkedinmail

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

One thought on “C# 7.0 – Generalized async return types