How to use async lambda with SelectMany?

CodingIntrigue picture CodingIntrigue · Nov 3, 2015 · Viewed 11.1k times · Source

I'm getting the following error when trying to use an async lambda within IEnumerable.SelectMany:

var result = myEnumerable.SelectMany(async (c) => await Functions.GetDataAsync(c.Id));

The type arguments for method 'IEnumerable System.Linq.Enumerable.SelectMany(this IEnumerable, Func>)' cannot be inferred from the usage. Try specifying the type arguments explicitly

Where GetDataAsync is defined as:

public interface IFunctions {
    Task<IEnumerable<DataItem>> GetDataAsync(string itemId);
}

public class Functions : IFunctions {
    public async Task<IEnumerable<DataItem>> GetDataAsync(string itemId) {
        // return await httpCall();
    }
}

I guess because my GetDataAsync method actually returns a Task<IEnumerable<T>>. But why does Select work, surely it should throw the same error?

var result = myEnumerable.Select(async (c) => await Functions.GetDataAsync(c.Id));

Is there any way around this?

Answer

SergeyGrudskiy picture SergeyGrudskiy · Mar 8, 2016

This is an extension:

public static async Task<IEnumerable<T1>> SelectManyAsync<T, T1>(this IEnumerable<T> enumeration, Func<T, Task<IEnumerable<T1>>> func)
{
    return (await Task.WhenAll(enumeration.Select(func))).SelectMany(s => s);
}

That allows you to run:

var result = await myEnumerable.SelectManyAsync(c => Functions.GetDataAsync(c.Id));

Explanation: you have a list of tasks, each returns Task<IEnumerable<T>>. So you need to fire them all, then await all, and then squash the result via SelectMany.