13

I have a .net core 3.1 console app.

I have a method with the following signature:

public async IAsyncEnumerable<string> GetFilePathsFromRelativePathAsync(string relativePath)

If I call it:

private async Task<IEnumerable<FileUpload>> GetFileUploadsAsync(string relativePath)
{
...
    var filePaths = await service.GetFilePathsFromRelativePathAsync(relativePath);
...
}

I get the following error:

Error CS1061 'IAsyncEnumerable' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'IAsyncEnumerable' could be found (are you missing a using directive or an assembly reference?)

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Murdock
  • 4,352
  • 3
  • 34
  • 63

1 Answers1

18

The correct syntax is :

await foreach(var filePath in service.GetFilePathsFromRelativePathAsync(relativePath))
{
    ....
}

An IAsyncEnumerable is used to return a stream of elements that can be processed individually. That's why the feature is actually called async streams, causing quite a bit of confusion

Converting to Task< IEnumerable< FileUpload>>

The best solution would be to not convert, but change the signature to IEnumerable<FileUpload> and return new FileUpload instances as soon as they're created :

private async IAsyncEnumerable<FileUpload> GetFileUploadsAsync(string relativePath)
{
    await foreach(var filePath in service.GetFilePathsFromRelativePathAsync(relativePath))
    {
        var upload = new FileUpload(filePath);
        yield return upload;
    }
}

You can also gather all results, store them in a list and return them, eg with a ToListAsync extension method :

public static async Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source, CancellationToken cancellationToken=default)
{
    var list = new List<T>();
    await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
    {
        list.Add(item);
    }

    return list;
}

The best code is code you don't write though. The System.Linq.Async project provides LINQ operators for IAsyncEnumerable, including ToList, and can be found on NuGet.

The code is very simple but includes a few optimizations, like using ValueTask instead of Task and special treatment for data that comes from other operators like GroupBy and Reverse, that have to consume the entire IAsyncEnumerable before producing their output.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Ok thats interesting... Is there any way then to go from IAsyncEnumerable to Task> without the foreach? – Murdock Feb 10 '20 at 19:48
  • You can change your method to `IAsyncEnumerable` instead, and `yield return` new FileUpload instances as soon as they're produced. Or you can use (or copy) the code in the [System.Linq.Async ToList](https://github.com/dotnet/reactive/blob/master/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToList.cs) operator to add each item to a list and return the list at the end – Panagiotis Kanavos Feb 11 '20 at 08:09
  • Please rename `from` to `to` in the first and second foreach loop samples. – Deano Sep 27 '20 at 02:00
  • @Deano you mean `in`? – Panagiotis Kanavos Sep 28 '20 at 08:35
  • Correct. Doh! Thanks mate. – Deano Sep 29 '20 at 23:47
  • @Deano thanks. I was probably experimenting with LINQ in LINQPad as I was writing that code and mixed up `from` and `in` – Panagiotis Kanavos Sep 30 '20 at 06:53