30

Take the following the methods:

public async IAsyncEnumerable<int> Foo()
{
   await SomeAsyncMethod();
   return Bar(); // Throws since you can not return values from iterators
}

public async IAsyncEnumerable<int> Bar()
{
   for(int i = 0; i < 10; i++)
   {
       await Task.Delay(100);
       yield return i;
   }
}

I wonder what the best practice would be, to do, what the code above tries to. Basically returning an IAsyncEnumerable from an async method.

For myself I can imagine two ways:

  1. Iterating over the IAsyncEnumerable and yielding the result immediately back.
await foreach(var item in Bar())
{
    yield return item;
}
  1. Creating a struct which can store an IAsyncEnumerable temporarily, which seems to be the better solution, but still kind of overkill.
return new AsyncEnumerableHolder<int>(Bar());

public struct AsyncEnumerableHolder<T>
{
    public readonly IAsyncEnumerable<T> Enumerable;

    public AsyncEnumerableHolder(IAsyncEnumerable<T> enumerable)
    {
        Enumerable = enumerable;
    }
}

Is there any better way to achieve this behavior?

Twenty
  • 5,234
  • 4
  • 32
  • 67
  • 1
    Pretty sure `async` expects you to return `void`, `Task`, or `Task` (`void` is almost always the wrong choice there though) – Powerlord Jan 10 '20 at 22:13
  • 1
    Well `public async IAsyncEnumerable Bar()` is totally valid in terms of syntax. I am not sure what you are trying to say. – Twenty Jan 10 '20 at 22:18
  • 1
    What I'm trying to say is that `async` only supports [certain return types](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/async-return-types). Having `IAsyncEnumerable` as the return type *should* be a compiler error as it doesn't declare a `GetAwaiter` method. – Powerlord Jan 10 '20 at 22:50
  • This is another question of I absolutely need X because I think it's the answer to my problem, however, X doesn't work. Instead, you should explain what are you trying to achieve, there is certainly an alternative. – aybe Jan 10 '20 at 22:53
  • 2
    @Powerlord Well, `IAsyncEnumerable` and `IAsyncEnumerator` return types are definitely supported in C#8 - [Asynchronous streams](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#asynchronous-streams) – Ivan Stoev Jan 11 '20 at 01:29
  • @IvanStoev In that case, I've filed a ticket against the page I previously linked commenting on the missing documentation. – Powerlord Jan 11 '20 at 02:20
  • @Powerlord That MSDN page has been updated to add: "Starting with C# 8.0, IAsyncEnumerable, for an async method that returns an async stream." – Ian Kemp Nov 02 '20 at 10:39

1 Answers1

43

The struct approach wouldn't work. If you want to asynchronously return an IAsyncEnumerator<T> value, you could use Task<IAsyncEnumerator<T>> with return Bar();. However, that would be unusual. It would be much more natural to create a new IAsyncEnumerator<T> that incorporates await SomeAsyncMethod() at the beginning of the asynchronous enumerable. To do this, you should use await and yield as suggested by your option (1):

public async IAsyncEnumerable<int> Foo()
{
  await SomeAsyncMethod();
  await foreach (var item in Bar())
    yield return item;
}

On a side note, JavaScript has a very nice yield* syntax for this kind of "enumerate this whole sequence into my result sequence" concept, and it supports both synchronous and asynchronous sequences. C# does not have this kind of syntax for either synchronous or asynchronous sequences.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 5
    Thanks for that one, it just feels so wrong to iterate it again. I really hope the `yield*` thingy or similar will every arrive to C#. – Twenty Jan 11 '20 at 15:24
  • 8
    @Twenty: You can follow [this issue](https://github.com/dotnet/csharplang/issues/378) if you're interested. – Stephen Cleary Jan 11 '20 at 23:23