In this article, we will learn how to convert IAsyncEnumerable to a List in C#. We’ll talk about some practical reasons for this conversion and explore the common method for achieving it.

To download the source code for this article, you can visit our GitHub repository.

So without wasting time, let’s get started.

The Reason to Convert IAsyncEnumerable to List

The IAsyncEnumerable<T>Ā interface, introduced in C# 8, offers a powerful tool for on-demand data delivery, enhancing efficiency in data processing. However, there are scenarios where converting it to a List<T> is not only necessary but also beneficial.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Let’s delve into some typical situations that necessitate this conversion.

One of the primary reasons for this conversion involves the need to process entire datasets immediately. This is particularly relevant when performing calculations, aggregations, or when the data needs to be fed into another synchronous operation. By converting to a list, we obtain a snapshot of the data, allowing for immediate manipulation and analysis.

Another scenario where this conversion becomes necessary is when integrating with libraries and frameworks that operate on traditional list-based data structures. Ensuring compatibility and seamless integration is vital, and converting IAsyncEnumerable<T> to List<T> facilitates this process.

With a clear understanding of the necessity for conversion, we can now explore how to achieve this conversion.

Using ToListAsync() Method to Convert IAsyncEnumerable

Letā€™s start with a method that simulates fetching users in batches, mimicking data arriving asynchronously from a real-world database. By yielding each user individually using yield return, it adheres to the IAsyncEnumerable<T> pattern, allowing us to efficiently process data as it becomes available:

static async IAsyncEnumerable<User> GetUsersAsync(int delayMilliseconds = 500)
{
    for (int page = 1; page <= 3; page++)
    {
        await Task.Delay(delayMilliseconds);

        var users = new List<User>
        {
            new User((page - 1) * 3 + 1, "Alice"),
            new User((page - 1) * 3 + 2, "Bob"),
            new User((page - 1) * 3 + 3, "John")
        };

        foreach (var user in users)
        {
            yield return user;
        }
    }
}

When it comes to converting IAsyncEnumerable<T> to List<T>, the built-in ToListAsync() method from the System.Linq.Async NuGet package offers a powerful and suitable option. It’s also worth noting that this method is different from the EntityFrameworkQueryableExtensionsĀ ToListAsync(IQueryable<T>) method.

Let’s see how to use it to convert our sample GetUserAsync() method:

static async Task<List<User>> GetUsersAsListAsync(int delayMilliseconds = 500)
{
    var usersAsync = GetUsersAsync(delayMilliseconds);

    return await usersAsync.ToListAsync();
}

Here, we can see that the conversion process is incredibly concise. By invokingĀ ToListAsync()Ā on anĀ IAsyncEnumerable<T>Ā instance and awaiting the result, we obtain a readily availableĀ List<T>.

To validate the output and confirm that it indeed returns a List<T>, we can utilize the built-in GetType() method:

var usersAsync = GetUsersAsync();
var usersList = await usersAsync.ToListAsync();

Console.WriteLine(usersList.GetType());

Using GetType(), we retrieve the underlying type of our userList variable and then write it to the console:

System.Collections.Generic.List`1[ConvertIAsyncEnumerableToListLibrary.Model.User]

Our output indicates that the conversion has resulted in a List<T>Ā collection, as expected.

Using ToListAsync() Method With Cancellation Token for Conversion

While the ToListAsync() method is simple and convenient for conversion, it is important to be aware of its possible drawbacks, particularly when dealing with potentially long-running asynchronous operations.

How conversion using this method works is that it buffers the entire dataset into memory before returning the List<T>. When working with huge datasets, this can result in memory exhaustion or performance bottlenecks, especially if the procedure takes longer than expected.

This is where the need to use a cancellation token comes in, acting as a sort of safeguard for our asynchronous operations. By providing a CancellationToken to the ToListAsync() method, we can cancel the data retrieval and processing if necessary.

To use the cancellation token in our method, we first have to modify our IAsyncEnumerable method GetUsersAsync() to respect the provided token. This is a great practice that ensures the operation can be canceled efficiently at any point during its execution:

static async IAsyncEnumerable<User> GetUsersAsync
(int delayMilliseconds = 500, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    for (int page = 1; page <= 3; page++)
    {
        await Task.Delay(delayMilliseconds, cancellationToken);

        var users = new List<User>
        {
            new User((page - 1) * 3 + 1, "Alice"),
            new User((page - 1) * 3 + 2, "Bob"),
            new User((page - 1) * 3 + 3, "John")
        };

        foreach (var user in users)
        {
            cancellationToken.ThrowIfCancellationRequested();
            yield return user;
        }
    }
}

In this modified version, we pass the CancellationToken to Task.Delay to allow the delay to be cancelable. Additionally, we call cancellationToken.ThrowIfCancellationRequested() before yielding each user, to ensure that the operation can be canceled at any point.

With theĀ CancellationTokenĀ now integrated into ourĀ GetUsersAsync()Ā method, we also need to adjust theĀ GetUsersAsListAsync()Ā method to support cancellation:

static async Task<List<User>> GetUsersAsListAsync
(int delayMilliseconds = 500, CancellationToken cancellationToken = default)
{
    var usersAsync = GetUsersAsync(delayMilliseconds, cancellationToken);

    return await usersAsync.ToListAsync(cancellationToken);
}

In our code, we pass theĀ CancellationToken to both the GetUsersAsync() and ToListAsync() methods. This setup ensures that if a cancellation is requested, it can be propagated through both the asynchronous enumeration and the conversion to a list, allowing the operation to be canceled at any point.

Check out our article for a detailed explanation of utilizing CancellationToken for asynchronous operations.

Conclusion

Throughout this article, we’ve looked at how to convert IAsyncEnumerable to a List in C#, delving into the reasoning behind it and the practical method for doing so. We also talked about the advantage of employing a cancellation token during this conversion.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!