An async stream is a sequence whose elements can be consumed as they become available. In C#, `IAsyncEnumerable<Comic>` lets a repository expose search results as an asynchronous sequence rather than a single `Task` containing the results. section 8.2
Return a repository query
A repository’s `Find` method filters comics by title and calls `AsAsyncEnumerable()` on the EF Core query. Its return type is `IAsyncEnumerable<Comic>`; adding and saving a comic instead uses `Task`-returning methods. section 8.2
Listing 8.6 Operating on an IAsyncEnumerable
private IAsyncEnumerable<Comic> FindComicsByText(
string searchText) { } #1
private async Task<Image> GetComicImageAsync(
string imgUrl) { } #1
public async IAsyncEnumerable<Comic> Find( #2
string searchText)
{
var foundComics = FindComicsByText(searchText); #3
await foreach (var comic in foundComics) #4
{
comic.Image = await GetComicImageAsync(comic.ImgUrl); #5
yield return comic; #6
}
}
Follow the per-element path: `await foreach` reads each comic, an awaited call obtains its image, and `yield return` emits the updated comic. Because this is an async iterator, `Find` returns `IAsyncEnumerable<Comic>` without wrapping it in `Task`. section 8.2
The return type changes when a method awaits preliminary work but does not enumerate the results itself. `FindAsync` fetches missing comics, then returns the repository’s `IAsyncEnumerable<Comic>` as an object. It therefore returns `Task<IAsyncEnumerable<Comic>>`: awaiting the task obtains the sequence; consuming the sequence is a separate step. section 8.4
Test the returned sequence
An integration test awaits `FindAsync("b")`, converts the resulting sequence with `ToBlockingEnumerable()`, and asserts that exactly one matching comic was found. SQLite backs the repository while a faked HTTP message handler supplies responses. section 8.4
