Search code examples
c#async-awaitcsvhelperc#-8.0iasyncenumerable

Run enumeration of IAsyncEnumerable twice not possible?


Run enumeration of IAsyncEnumerable twice not possible?

Once CountAsync has been run, the await foreach won't enumerate any item. Why? It seems there is no Reset method on the AsyncEnumerator.

var count = await itemsToImport.CountAsync();

await foreach (var importEntity in itemsToImport)
{
    // won't run
}

Source of data:

private IAsyncEnumerable<TEntity> InternalImportFromStream(TextReader reader)
{
    var csvReader = new CsvReader(reader, Config);
        
    return csvReader.GetRecordsAsync<TEntity>();
}

Solution

  • This has nothing to do with resetting an IAsyncEnumerator. This code attempts to generate a second IAsyncEnumerator, which, just like with IEnumerable.GetEnumerator() is only possible on some kinds of collections. If the Enumerable (async or not) is an abstraction over some sort of forward-only data structure, then GetEnumerator/GetAsyncEnumerator will fail.

    And even when it doesn't fail, it's sometimes expensive. For instance it might run a database query or hit an remote API each time it's enumerated. This is why IEnumerable/IAsyncEnumerable make poor public return types from functions, as they fail to describe the capabilities of the returned collection, and almost the only thing you can do with the value is materialize it with .ToList/ToListAsync.

    Eg, this works fine:

    static async IAsyncEnumerable<int> Col()
    {
        for (int i = 1; i <= 10; i++)
        {
            yield return i;
        }
    }
    static void Main(string[] args)
    {
        Run().Wait();
    }
    static async Task Run()
    {
    
        var col = Col();
    
        var count = await col.CountAsync();
        await foreach (var dataPoint in col)
        {
            Console.WriteLine(dataPoint);
        }
    }