File: System\Linq\AsyncIterator.cs
Web Access
Project: src\src\libraries\System.Linq.AsyncEnumerable\src\System.Linq.AsyncEnumerable.csproj (System.Linq.AsyncEnumerable)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Linq
{
    public static partial class AsyncEnumerable
    {
        /// <summary>
        /// A base class for async enumerables that are loaded on-demand.
        /// </summary>
        /// <typeparam name="TSource">The type of each item to yield.</typeparam>
        /// <remarks>
        /// <list type="bullet">
        /// <item><description>
        /// The value of an async iterator is immutable; the operation it represents cannot be changed.
        /// </description></item>
        /// <item><description>
        /// However, an async iterator also serves as its own enumerator, so the state of an async iterator
        /// may change as it is being enumerated.
        /// </description></item>
        /// <item><description>
        /// Hence, state that is relevant to an async iterator's value should be kept in readonly fields.
        /// State that is relevant to an async iterator's enumeration (such as the currently yielded item)
        /// should be kept in non-readonly fields.
        /// </description></item>
        /// </list>
        /// </remarks>
        private abstract class AsyncIterator<TSource> : IAsyncEnumerable<TSource>, IAsyncEnumerator<TSource>
        {
            private readonly int _threadId = Environment.CurrentManagedThreadId;
 
            private protected int _state;
            private protected TSource _current = default!;
            private protected CancellationToken _cancellationToken;
 
            /// <summary>
            /// The item currently yielded by this iterator.
            /// </summary>
            public TSource Current => _current;
 
            /// <summary>
            /// Makes a shallow copy of this iterator.
            /// </summary>
            /// <remarks>
            /// This method is called if <see cref="GetAsyncEnumerator"/> is called more than once.
            /// </remarks>
            private protected abstract AsyncIterator<TSource> Clone();
 
            /// <summary>
            /// Puts this iterator in a state whereby no further enumeration will take place.
            /// </summary>
            /// <remarks>
            /// Derived classes should override this method if necessary to clean up any
            /// mutable state they hold onto (for example, calling DisposeAsync on other enumerators).
            /// </remarks>
            public virtual ValueTask DisposeAsync()
            {
                _current = default!;
                _state = -1;
                return default;
            }
 
            /// <summary>
            /// Gets the enumerator used to yield values from this iterator.
            /// </summary>
            /// <remarks>
            /// If <see cref="GetAsyncEnumerator"/> is called for the first time on the same thread
            /// that created this iterator, the result will be this iterator. Otherwise, the result
            /// will be a shallow copy of this iterator.
            /// </remarks>
            public IAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default)
            {
                AsyncIterator<TSource> enumerator = _state == 0 && _threadId == Environment.CurrentManagedThreadId ? this : Clone();
                enumerator._state = 1;
                enumerator._cancellationToken = cancellationToken;
                return enumerator;
            }
 
            /// <summary>
            /// Retrieves the next item in this iterator and yields it via <see cref="Current"/>.
            /// </summary>
            /// <returns><c>true</c> if there was another value to be yielded; otherwise, <c>false</c>.</returns>
            public abstract ValueTask<bool> MoveNextAsync();
        }
    }
}