File: System\Linq\Reverse.cs
Web Access
Project: src\src\libraries\System.Linq\src\System.Linq.csproj (System.Linq)
// 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.Diagnostics;
 
namespace System.Linq
{
    public static partial class Enumerable
    {
        public static IEnumerable<TSource> Reverse<TSource>(this IEnumerable<TSource> source)
        {
            if (source is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
            }
 
            if (IsEmptyArray(source))
            {
                return [];
            }
 
            return new ReverseIterator<TSource>(source);
        }
 
        public static IEnumerable<TSource> Reverse<TSource>(this TSource[] source)
        {
            if (source is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
            }
 
            if (source.Length == 0)
            {
                return [];
            }
 
            return new ReverseIterator<TSource>(source);
        }
 
        /// <summary>
        /// An iterator that yields the items of an <see cref="IEnumerable{TSource}"/> in reverse.
        /// </summary>
        /// <typeparam name="TSource">The type of the source enumerable.</typeparam>
        private sealed partial class ReverseIterator<TSource> : Iterator<TSource>
        {
            private readonly IEnumerable<TSource> _source;
            private TSource[]? _buffer;
 
            public ReverseIterator(IEnumerable<TSource> source)
            {
                Debug.Assert(source is not null);
                _source = source;
            }
 
            private protected override Iterator<TSource> Clone() => new ReverseIterator<TSource>(_source);
 
            public override bool MoveNext()
            {
                if (_state - 2 <= -2)
                {
                    // Either someone called a method and cast us to IEnumerable without calling GetEnumerator,
                    // or we were already disposed. In either case, iteration has ended, so return false.
                    // A comparison is made against -2 instead of _state <= 0 because we want to handle cases where
                    // the source is really large and adding the bias causes _state to overflow.
                    Debug.Assert(_state == -1 || _state == 0);
                    Dispose();
                    return false;
                }
 
                switch (_state)
                {
                    case 1:
                        // Iteration has just started. Capture the source into an array and set _state to 2 + the count.
                        // Having an extra field for the count would be more readable, but we save it into _state with a
                        // bias instead to minimize field size of the iterator.
                        TSource[] buffer = _source.ToArray();
                        _buffer = buffer;
                        _state = buffer.Length + 2;
                        goto default;
                    default:
                        // At this stage, _state starts from 2 + the count. _state - 3 represents the current index into the
                        // buffer. It is continuously decremented until it hits 2, which means that we've run out of items to
                        // yield and should return false.
                        int index = _state - 3;
                        if (index != -1)
                        {
                            Debug.Assert(_buffer is not null);
                            _current = _buffer[index];
                            --_state;
                            return true;
                        }
 
                        break;
                }
 
                Dispose();
                return false;
            }
 
            public override void Dispose()
            {
                _buffer = null; // Just in case this ends up being long-lived, allow the memory to be reclaimed.
                base.Dispose();
            }
        }
    }
}