IAsyncEnumerator<T> asynchronously advances through a sequence and exposes its current value. Current returns T, MoveNextAsync returns ValueTask<bool>, and DisposeAsync returns ValueTask. section 14.1
IAsyncEnumerable<T>.GetAsyncEnumerator returns the enumerator quickly; lengthy asynchronous initialization should begin when MoveNextAsync is first called. await foreach performs the await required at each iteration. section 14.1 section 14.8
Asynchronous iterator
An async iterator can yield 1, await Task.Delay(1000), then yield 2. The consumer uses await foreach to process each value as it becomes available. section 14.2
Table 14.2 IEnumerator vs. IAsyncEnumerator (view table figure)
| IEnumerator<T> |
IAsyncEnumerator<T> |
|---|---|
T Current {get;}
|
T Current {get;}
|
bool MoveNext() |
ValueTask<bool> MoveNextAsync() |
void Dispose() |
ValueTask DisposeAsync() |
Compare IEnumerator<T> with IAsyncEnumerator<T>, including Current, MoveNextAsync, and asynchronous disposal. section 14.1
Listing 14.3 Code generated by the compiler from listing 14.2
public class AsyncYieldDemo_Enumerable : IAsyncEnumerable<int>
{
public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken _)
{
return new YieldDemo_Enumerator();
}
}
public class YieldDemo_Enumerator : IAsyncEnumerator<int>
{
public int Current { get; private set; }
private async Task Step0()
{
Current = 1;
}
private async Task Step1()
{
await Task.Delay(1000);
Current = 2;
}
private int _step = 0;
public async ValueTask<bool> MoveNextAsync()
{
switch(_step)
{
case 0:
await Step0();
++_step;
break;
case 1:
await Step1();
++_step;
break;
case 2:
return false;
}
return true;
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
public IAsyncEnumerable<int> AsyncYieldDemo()
{
return new AsyncYieldDemo_Enumerable();
}
Inspect the compiler-shaped enumerator, including generated state steps and MoveNextAsync. section 14.2
