I'd like to know if there's a way I can write a function to "pass through" an IAsyncEnumerable... that is, the function will call another IAsyncEnumerable function and yield all results without having to write a foreach
to do it?
I find myself writing this code pattern a lot. Here's an example:
async IAsyncEnumerable<string> MyStringEnumerator();
async IAsyncEnumerable<string> MyFunction()
{
// ...do some code...
// Return all elements of the whole stream from the enumerator
await foreach(var s in MyStringEnumerator())
{
yield return s;
}
}
For whatever reason (due to layered design) my function MyFunction
wants to call MyStringEnumerator
but then just yield everything without intervention. I have to keep writing these foreach
loops to do it. If it were an IEnumerable
I would return the IEnumerable
. If it were C++ I could write a macro to do it.
What's best practice?
IAsyncEnumerable<T> exposes an enumerator that has a MoveNextAsync() method that can be awaited. This means a method that produces this result can make asynchronous calls in between yielding results. Cool! This method can now yield data asynchronously.
IAsyncEnumerable<T> Interface Exposes an enumerator that provides asynchronous iteration over values of a specified type.
WhenAll(IEnumerable<Task>) Creates a task that will complete when all of the Task objects in an enumerable collection have completed. WhenAll(Task[]) Creates a task that will complete when all of the Task objects in an array have completed.
If it were an IEnumerable I would return the IEnumerable.
Well, you can just do the same thing with IAsyncEnumerable
(note that the async
is removed):
IAsyncEnumerable<string> MyFunction()
{
// ...do some code...
// Return all elements of the whole stream from the enumerator
return MyStringEnumerator();
}
However, there's an important semantic consideration here. When calling an enumerator method, the ...do some code...
will be executed immediately, and not when the enumerator is enumerated.
// (calling code)
var enumerator = MyFunction(); // `...do some code...` is executed here
...
await foreach (var s in enumerator) // it's not executed here when getting the first `s`
...
This is true for both synchronous and asynchronous enumerables.
If you want ...do some code...
to be executed when the enumerator is enumerated, then you'll need to use the foreach
/yield
loop to get the deferred execution semantics:
async IAsyncEnumerable<string> MyFunction()
{
// ...do some code...
// Return all elements of the whole stream from the enumerator
await foreach(var s in MyStringEnumerator())
yield return s;
}
And you would have to use the same pattern in the synchronous world if you wanted deferred execution semantics with a synchronous enumerable, too:
IEnumerable<string> ImmediateExecution()
{
// ...do some code...
// Return all elements of the whole stream from the enumerator
return MyStringEnumerator();
}
IEnumerable<string> DeferredExecution()
{
// ...do some code...
// Return all elements of the whole stream from the enumerator
foreach(var s in MyStringEnumerator())
yield return s;
}
Returning Task<IAsyncEnumerable<Obj>>
from the calling method seems to work
async IAsyncEnumerable<string> MyStringEnumerator();
async Task<IAsyncEnumerable<string>> MyFunction()
{
await Something();
return MyStringEnumerator();
}
You'll then need to await MyFunction(). So to use in an async foreach would be
await foreach (string s in await MyFunction()) {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With