File: System\Buffers\ReadOnlySequence.cs
Web Access
Project: src\src\libraries\System.Memory\src\System.Memory.csproj (System.Memory)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace System.Buffers
{
    /// <summary>
    /// Represents a sequence that can read a sequential series of <typeparam name="T" />.
    /// </summary>
    [DebuggerTypeProxy(typeof(ReadOnlySequenceDebugView<>))]
    [DebuggerDisplay("{ToString(),raw}")]
    public readonly partial struct ReadOnlySequence<T>
    {
        // The data is essentially two SequencePositions, however the Start and End SequencePositions are deconstructed to improve packing.
        private readonly object? _startObject;
        private readonly object? _endObject;
        private readonly int _startInteger;
        private readonly int _endInteger;
 
        /// <summary>
        /// Returns empty <see cref="ReadOnlySequence{T}"/>
        /// </summary>
        public static readonly ReadOnlySequence<T> Empty = new ReadOnlySequence<T>(Array.Empty<T>());
 
        /// <summary>
        /// Length of the <see cref="ReadOnlySequence{T}"/>.
        /// </summary>
        public long Length => GetLength();
 
        /// <summary>
        /// Determines if the <see cref="ReadOnlySequence{T}"/> is empty.
        /// </summary>
        public bool IsEmpty => Length == 0;
 
        /// <summary>
        /// Determines if the <see cref="ReadOnlySequence{T}"/> contains a single <see cref="ReadOnlyMemory{T}"/> segment.
        /// </summary>
        public bool IsSingleSegment
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => _startObject == _endObject;
        }
 
        /// <summary>
        /// Gets <see cref="ReadOnlyMemory{T}"/> from the first segment.
        /// </summary>
        public ReadOnlyMemory<T> First => GetFirstBuffer();
 
        /// <summary>
        /// Gets <see cref="ReadOnlySpan{T}"/> from the first segment.
        /// </summary>
        public ReadOnlySpan<T> FirstSpan => GetFirstSpan();
 
        /// <summary>
        /// A position to the start of the <see cref="ReadOnlySequence{T}"/>.
        /// </summary>
        public SequencePosition Start
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => new SequencePosition(_startObject, GetIndex(_startInteger));
        }
 
        /// <summary>
        /// A position to the end of the <see cref="ReadOnlySequence{T}"/>
        /// </summary>
        public SequencePosition End
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => new SequencePosition(_endObject, GetIndex(_endInteger));
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ReadOnlySequence(object? startSegment, int startIndexAndFlags, object? endSegment, int endIndexAndFlags)
        {
            // Used by SliceImpl to create new ReadOnlySequence
 
            // startSegment and endSegment can be null for default ReadOnlySequence only
            Debug.Assert((startSegment != null && endSegment != null) ||
                (startSegment == null && endSegment == null && startIndexAndFlags == 0 && endIndexAndFlags == 0));
 
            _startObject = startSegment;
            _endObject = endSegment;
            _startInteger = startIndexAndFlags;
            _endInteger = endIndexAndFlags;
        }
 
        /// <summary>
        /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from linked memory list represented by start and end segments
        /// and corresponding indexes in them.
        /// </summary>
        public ReadOnlySequence(ReadOnlySequenceSegment<T> startSegment, int startIndex, ReadOnlySequenceSegment<T> endSegment, int endIndex)
        {
            if (startSegment == null ||
                endSegment == null ||
                (startSegment != endSegment && startSegment.RunningIndex > endSegment.RunningIndex) ||
                (uint)startSegment.Memory.Length < (uint)startIndex ||
                (uint)endSegment.Memory.Length < (uint)endIndex ||
                (startSegment == endSegment && endIndex < startIndex))
                ThrowHelper.ThrowArgumentValidationException(startSegment, startIndex, endSegment);
 
            _startObject = startSegment;
            _endObject = endSegment;
            _startInteger = startIndex;
            _endInteger = endIndex;
        }
 
        /// <summary>
        /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the array.
        /// </summary>
        public ReadOnlySequence(T[] array)
        {
            if (array == null)
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
 
            _startObject = array;
            _endObject = array;
            _startInteger = 0;
            _endInteger = ReadOnlySequence.ArrayToSequenceEnd(array.Length);
        }
 
        /// <summary>
        /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the array, start, and index.
        /// </summary>
        public ReadOnlySequence(T[] array, int start, int length)
        {
            if (array == null ||
                (uint)start > (uint)array.Length ||
                (uint)length > (uint)(array.Length - start))
                ThrowHelper.ThrowArgumentValidationException(array, start);
 
            _startObject = array;
            _endObject = array;
            _startInteger = start;
            _endInteger = ReadOnlySequence.ArrayToSequenceEnd(start + length);
        }
 
        /// <summary>
        /// Creates an instance of <see cref="ReadOnlySequence{T}"/> from the <see cref="ReadOnlyMemory{T}"/>.
        /// Consumer is expected to manage lifetime of memory until <see cref="ReadOnlySequence{T}"/> is not used anymore.
        /// </summary>
        public ReadOnlySequence(ReadOnlyMemory<T> memory)
        {
            if (MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager<T>? manager, out int index, out int length))
            {
                _startObject = manager;
                _endObject = manager;
                _startInteger = ReadOnlySequence.MemoryManagerToSequenceStart(index);
                _endInteger = index + length;
            }
            else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
            {
                T[]? array = segment.Array;
                int start = segment.Offset;
                _startObject = array;
                _endObject = array;
                _startInteger = start;
                _endInteger = ReadOnlySequence.ArrayToSequenceEnd(start + segment.Count);
            }
            else if (typeof(T) == typeof(char))
            {
                if (!MemoryMarshal.TryGetString((ReadOnlyMemory<char>)(object)memory, out string? text, out int start, out length))
                    ThrowHelper.ThrowInvalidOperationException();
 
                _startObject = text;
                _endObject = text;
                _startInteger = ReadOnlySequence.StringToSequenceStart(start);
                _endInteger = ReadOnlySequence.StringToSequenceEnd(start + length);
            }
            else
            {
                // Should never be reached
                ThrowHelper.ThrowInvalidOperationException();
                _startObject = null;
                _endObject = null;
                _startInteger = 0;
                _endInteger = 0;
            }
        }
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items.
        /// </summary>
        /// <param name="start">The index at which to begin this slice.</param>
        /// <param name="length">The length of the slice.</param>
        /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at index <paramref name="start" />.</returns>
        public ReadOnlySequence<T> Slice(long start, long length)
        {
            if (start < 0 || length < 0)
                ThrowHelper.ThrowStartOrEndArgumentValidationException(start);
 
            SequencePosition begin;
            SequencePosition end;
 
            int startIndex = GetIndex(_startInteger);
            int endIndex = GetIndex(_endInteger);
 
            object? startObject = _startObject;
            object? endObject = _endObject;
 
            if (startObject != endObject)
            {
                Debug.Assert(startObject != null);
                var startSegment = (ReadOnlySequenceSegment<T>)startObject;
 
                int currentLength = startSegment.Memory.Length - startIndex;
 
                // Position in start segment
                if (currentLength > start)
                {
                    startIndex += (int)start;
                    begin = new SequencePosition(startObject, startIndex);
 
                    end = GetEndPosition(startSegment, startObject, startIndex, endObject!, endIndex, length);
                }
                else
                {
                    if (currentLength < 0)
                        ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
 
                    begin = SeekMultiSegment(startSegment.Next!, endObject!, endIndex, start - currentLength, ExceptionArgument.start);
 
                    int beginIndex = begin.GetInteger();
                    object beginObject = begin.GetObject()!;
 
                    if (beginObject != endObject)
                    {
                        Debug.Assert(beginObject != null);
                        end = GetEndPosition((ReadOnlySequenceSegment<T>)beginObject, beginObject, beginIndex, endObject!, endIndex, length);
                    }
                    else
                    {
                        if (endIndex - beginIndex < length)
                            ThrowHelper.ThrowStartOrEndArgumentValidationException(0);  // Passing value >= 0 means throw exception on length argument
 
                        end = new SequencePosition(beginObject, beginIndex + (int)length);
                    }
                }
            }
            else
            {
                if (endIndex - startIndex < start)
                    ThrowHelper.ThrowStartOrEndArgumentValidationException(-1); // Passing value < 0 means throw exception on start argument
 
                startIndex += (int)start;
                begin = new SequencePosition(startObject, startIndex);
 
                if (endIndex - startIndex < length)
                    ThrowHelper.ThrowStartOrEndArgumentValidationException(0);  // Passing value >= 0 means throw exception on length argument
 
                end = new SequencePosition(startObject, startIndex + (int)length);
            }
 
            return SliceImpl(begin, end);
        }
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/> and ending at <paramref name="end"/> (exclusive).
        /// </summary>
        /// <param name="start">The index at which to begin this slice.</param>
        /// <param name="end">The ending (exclusive) <see cref="SequencePosition"/> of the slice.</param>
        /// <returns>A slice that consists of items from the <paramref name="start" /> index to, but not including, the <paramref name="end" /> sequence position in the current read-only sequence.</returns>
        public ReadOnlySequence<T> Slice(long start, SequencePosition end)
        {
            if (start < 0)
                ThrowHelper.ThrowStartOrEndArgumentValidationException(start);
 
            uint startIndex = (uint)GetIndex(_startInteger);
            object? startObject = _startObject;
 
            uint endIndex = (uint)GetIndex(_endInteger);
            object? endObject = _endObject;
 
            uint sliceEndIndex = (uint)end.GetInteger();
            object? sliceEndObject = end.GetObject();
 
            if (sliceEndObject == null)
            {
                sliceEndObject = _startObject;
                sliceEndIndex = startIndex;
            }
 
            // Single-Segment Sequence
            if (startObject == endObject)
            {
                if (!InRange(sliceEndIndex, startIndex, endIndex))
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
                }
 
                if (sliceEndIndex - startIndex < start)
                    ThrowHelper.ThrowStartOrEndArgumentValidationException(-1); // Passing value < 0 means throw exception on start argument
 
                goto FoundInFirstSegment;
            }
 
            // Multi-Segment Sequence
            var startSegment = (ReadOnlySequenceSegment<T>)startObject!;
            ulong startRange = (ulong)(startSegment.RunningIndex + startIndex);
            ulong sliceRange = (ulong)(((ReadOnlySequenceSegment<T>)sliceEndObject!).RunningIndex + sliceEndIndex);
 
            // This optimization works because we know sliceEndIndex, startIndex, and endIndex are all >= 0
            Debug.Assert(sliceEndIndex >= 0 && startIndex >= 0 && endIndex >= 0);
            if (!InRange(
                sliceRange,
                startRange,
                (ulong)(((ReadOnlySequenceSegment<T>)endObject!).RunningIndex + endIndex)))
            {
                ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
            }
 
            if (startRange + (ulong)start > sliceRange)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
            }
 
            int currentLength = startSegment.Memory.Length - (int)startIndex;
 
            // Position not in startSegment
            if (currentLength <= start)
            {
                if (currentLength < 0)
                    ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
 
                // End of segment. Move to start of next.
                SequencePosition begin = SeekMultiSegment(startSegment.Next!, sliceEndObject, (int)sliceEndIndex, start - currentLength, ExceptionArgument.start);
                return SliceImpl(begin, end);
            }
 
        FoundInFirstSegment:
            // startIndex + start <= int.MaxValue
            Debug.Assert(start <= int.MaxValue - startIndex);
            return SliceImpl(new SequencePosition(startObject, (int)startIndex + (int)start), new SequencePosition(sliceEndObject, (int)sliceEndIndex));
        }
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items.
        /// </summary>
        /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param>
        /// <param name="length">The length of the slice.</param>
        /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at sequence position <paramref name="start" />.</returns>
        public ReadOnlySequence<T> Slice(SequencePosition start, long length)
        {
            uint startIndex = (uint)GetIndex(_startInteger);
            object? startObject = _startObject;
 
            uint endIndex = (uint)GetIndex(_endInteger);
            object? endObject = _endObject;
 
            // Check start before length
            uint sliceStartIndex = (uint)start.GetInteger();
            object? sliceStartObject = start.GetObject();
 
            if (sliceStartObject == null)
            {
                sliceStartIndex = startIndex;
                sliceStartObject = _startObject;
            }
 
            // Single-Segment Sequence
            if (startObject == endObject)
            {
                if (!InRange(sliceStartIndex, startIndex, endIndex))
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
                }
 
                if (length < 0)
                    // Passing value >= 0 means throw exception on length argument
                    ThrowHelper.ThrowStartOrEndArgumentValidationException(0);
 
                if (endIndex - sliceStartIndex < length)
                    ThrowHelper.ThrowStartOrEndArgumentValidationException(0);
 
                goto FoundInFirstSegment;
            }
 
            // Multi-Segment Sequence
            var sliceStartSegment = (ReadOnlySequenceSegment<T>)sliceStartObject!;
            ulong sliceRange = (ulong)((sliceStartSegment.RunningIndex + sliceStartIndex));
            ulong startRange = (ulong)(((ReadOnlySequenceSegment<T>)startObject!).RunningIndex + startIndex);
            ulong endRange = (ulong)(((ReadOnlySequenceSegment<T>)endObject!).RunningIndex + endIndex);
 
            // This optimization works because we know sliceStartIndex, startIndex, and endIndex are all >= 0
            Debug.Assert(sliceStartIndex >= 0 && startIndex >= 0 && endIndex >= 0);
            if (!InRange(sliceRange, startRange, endRange))
            {
                ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
            }
 
            if (length < 0)
                // Passing value >= 0 means throw exception on length argument
                ThrowHelper.ThrowStartOrEndArgumentValidationException(0);
 
            if (sliceRange + (ulong)length > endRange)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
            }
 
            int currentLength = sliceStartSegment.Memory.Length - (int)sliceStartIndex;
 
            // Position not in startSegment
            if (currentLength < length)
            {
                if (currentLength < 0)
                    ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
 
                // End of segment. Move to start of next.
                SequencePosition end = SeekMultiSegment(sliceStartSegment.Next!, endObject, (int)endIndex, length - currentLength, ExceptionArgument.length);
                return SliceImpl(start, end);
            }
 
        FoundInFirstSegment:
            // sliceStartIndex + length <= int.MaxValue
            Debug.Assert(length <= int.MaxValue - sliceStartIndex);
            return SliceImpl(new SequencePosition(sliceStartObject, (int)sliceStartIndex), new SequencePosition(sliceStartObject, (int)sliceStartIndex + (int)length));
        }
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items.
        /// </summary>
        /// <param name="start">The index at which to begin this slice.</param>
        /// <param name="length">The length of the slice.</param>
        /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at index <paramref name="start" />.</returns>
        public ReadOnlySequence<T> Slice(int start, int length) => Slice((long)start, length);
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/> and ending at <paramref name="end"/> (exclusive).
        /// </summary>
        /// <param name="start">The index at which to begin this slice.</param>
        /// <param name="end">The ending (exclusive) <see cref="SequencePosition"/> of the slice.</param>
        /// <returns>A slice that consists of items from the <paramref name="start" /> index to, but not including, the <paramref name="end" /> sequence position in the current read-only sequence.</returns>
        public ReadOnlySequence<T> Slice(int start, SequencePosition end) => Slice((long)start, end);
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, with <paramref name="length"/> items.
        /// </summary>
        /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param>
        /// <param name="length">The length of the slice.</param>
        /// <returns>A slice that consists of <paramref name="length" /> elements from the current instance starting at sequence position <paramref name="start" />.</returns>
        public ReadOnlySequence<T> Slice(SequencePosition start, int length) => Slice(start, (long)length);
 
        /// <summary>
        /// Forms a slice out of the given <see cref="ReadOnlySequence{T}"/>, beginning at <paramref name="start"/>, ending at <paramref name="end"/> (exclusive).
        /// </summary>
        /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param>
        /// <param name="end">The ending (exclusive) <see cref="SequencePosition"/> of the slice.</param>
        /// <returns>A slice that consists of items from the <paramref name="start" /> sequence position to, but not including, the <paramref name="end" /> sequence position in the current read-only sequence.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ReadOnlySequence<T> Slice(SequencePosition start, SequencePosition end)
        {
            BoundsCheck((uint)start.GetInteger(), start.GetObject(), (uint)end.GetInteger(), end.GetObject());
            return SliceImpl(start, end);
        }
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}" />, beginning at a specified sequence position and continuing to the end of the read-only sequence.
        /// </summary>
        /// <param name="start">The starting (inclusive) <see cref="SequencePosition"/> at which to begin this slice.</param>
        /// <returns>A slice starting at sequence position <paramref name="start" /> and continuing to the end of the current read-only sequence.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ReadOnlySequence<T> Slice(SequencePosition start)
        {
            bool positionIsNotNull = start.GetObject() != null;
            BoundsCheck(start, positionIsNotNull);
            return SliceImpl(positionIsNotNull ? start : Start);
        }
 
        /// <summary>
        /// Forms a slice out of the current <see cref="ReadOnlySequence{T}" /> , beginning at a specified index and continuing to the end of the read-only sequence.
        /// </summary>
        /// <param name="start">The start index at which to begin this slice.</param>
        /// <returns>A slice starting at index <paramref name="start" /> and continuing to the end of the current read-only sequence.</returns>
        public ReadOnlySequence<T> Slice(long start)
        {
            if (start < 0)
                ThrowHelper.ThrowStartOrEndArgumentValidationException(start);
 
            if (start == 0)
                return this;
 
            SequencePosition begin = Seek(start, ExceptionArgument.start);
            return SliceImpl(begin);
        }
 
        /// <inheritdoc />
        public override string ToString()
        {
            if (typeof(T) == typeof(char))
            {
                ReadOnlySequence<T> localThis = this;
                ReadOnlySequence<char> charSequence = Unsafe.As<ReadOnlySequence<T>, ReadOnlySequence<char>>(ref localThis);
 
                if (charSequence.TryGetString(out string? text, out int start, out int length))
                {
                    return text.Substring(start, length);
                }
 
                if (Length < int.MaxValue)
                {
                    return string.Create((int)Length, charSequence, (span, sequence) => sequence.CopyTo(span));
                }
            }
 
            return $"System.Buffers.ReadOnlySequence<{typeof(T).Name}>[{Length}]";
        }
 
        /// <summary>
        /// Returns an enumerator over the <see cref="ReadOnlySequence{T}"/>
        /// </summary>
        public Enumerator GetEnumerator() => new Enumerator(this);
 
        /// <summary>
        /// Returns a new <see cref="SequencePosition"/> at an <paramref name="offset"/> from the start of the sequence.
        /// </summary>
        public SequencePosition GetPosition(long offset)
        {
            if (offset < 0)
                ThrowHelper.ThrowArgumentOutOfRangeException_OffsetOutOfRange();
 
            return Seek(offset);
        }
 
        /// <summary>
        /// Returns the offset of a <paramref name="position" /> within this sequence from the start.
        /// </summary>
        /// <param name="position">The <see cref="System.SequencePosition"/> of which to get the offset.</param>
        /// <returns>The offset from the start of the sequence.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">The position is out of range.</exception>
        public long GetOffset(SequencePosition position)
        {
            object? positionSequenceObject = position.GetObject();
            bool positionIsNull = positionSequenceObject == null;
            BoundsCheck(position, !positionIsNull);
 
            object? startObject = _startObject;
            object? endObject = _endObject;
 
            uint positionIndex = (uint)position.GetInteger();
 
            // if sequence object is null we suppose start segment
            if (positionIsNull)
            {
                positionSequenceObject = _startObject;
                positionIndex = (uint)GetIndex(_startInteger);
            }
 
            // Single-Segment Sequence
            if (startObject == endObject)
            {
                return positionIndex;
            }
            else
            {
                // Verify position validity, this is not covered by BoundsCheck for Multi-Segment Sequence
                // BoundsCheck for Multi-Segment Sequence check only validity inside current sequence but not for SequencePosition validity.
                // For single segment position bound check is implicit.
                Debug.Assert(positionSequenceObject != null);
 
                if (((ReadOnlySequenceSegment<T>)positionSequenceObject).Memory.Length - positionIndex < 0)
                    ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
 
                // Multi-Segment Sequence
                ReadOnlySequenceSegment<T>? currentSegment = (ReadOnlySequenceSegment<T>?)startObject;
                while (currentSegment != null && currentSegment != positionSequenceObject)
                {
                    currentSegment = currentSegment.Next!;
                }
 
                // Hit the end of the segments but didn't find the segment
                if (currentSegment is null)
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException_PositionOutOfRange();
                }
 
                Debug.Assert(currentSegment!.RunningIndex + positionIndex >= 0);
 
                return currentSegment!.RunningIndex + positionIndex;
            }
        }
 
        /// <summary>
        /// Returns a new <see cref="SequencePosition"/> at an <paramref name="offset"/> from the <paramref name="origin"/>
        /// </summary>
        public SequencePosition GetPosition(long offset, SequencePosition origin)
        {
            if (offset < 0)
                ThrowHelper.ThrowArgumentOutOfRangeException_OffsetOutOfRange();
 
            return Seek(origin, offset);
        }
 
        /// <summary>
        /// Tries to retrieve next segment after <paramref name="position"/> and return its contents in <paramref name="memory"/>.
        /// Returns <code>false</code> if end of <see cref="ReadOnlySequence{T}"/> was reached otherwise <code>true</code>.
        /// Sets <paramref name="position"/> to the beginning of next segment if <paramref name="advance"/> is set to <code>true</code>.
        /// </summary>
        public bool TryGet(ref SequencePosition position, out ReadOnlyMemory<T> memory, bool advance = true)
        {
            bool result = TryGetBuffer(position, out memory, out SequencePosition next);
            if (advance)
            {
                position = next;
            }
 
            return result;
        }
 
        /// <summary>
        /// An enumerator over the <see cref="ReadOnlySequence{T}"/>
        /// </summary>
        public struct Enumerator
        {
            private readonly ReadOnlySequence<T> _sequence;
            private SequencePosition _next;
            private ReadOnlyMemory<T> _currentMemory;
 
            /// <summary>Initialize the enumerator.</summary>
            /// <param name="sequence">The <see cref="ReadOnlySequence{T}"/> to enumerate.</param>
            public Enumerator(in ReadOnlySequence<T> sequence)
            {
                _currentMemory = default;
                _next = sequence.Start;
                _sequence = sequence;
            }
 
            /// <summary>
            /// The current <see cref="ReadOnlyMemory{T}"/>
            /// </summary>
            public ReadOnlyMemory<T> Current => _currentMemory;
 
            /// <summary>
            /// Moves to the next <see cref="ReadOnlyMemory{T}"/> in the <see cref="ReadOnlySequence{T}"/>
            /// </summary>
            /// <returns></returns>
            public bool MoveNext()
            {
                if (_next.GetObject() == null)
                {
                    return false;
                }
 
                return _sequence.TryGet(ref _next, out _currentMemory);
            }
        }
 
        private enum SequenceType
        {
            MultiSegment = 0x00,
            Array = 0x1,
            MemoryManager = 0x2,
            String = 0x3,
        }
    }
 
    internal static class ReadOnlySequence
    {
        /// <summary>
        /// Flag that allows encoding the <see cref="ReadOnlySequence{T}.SequenceType"/>.
        /// </summary>
        /// <seealso cref="ReadOnlySequence{T}.GetSequenceType"/>
        public const int FlagBitMask = 1 << 31;
        public const int IndexBitMask = ~FlagBitMask;
 
        public const int ArrayEndMask = FlagBitMask;
 
        public const int MemoryManagerStartMask = FlagBitMask;
 
        public const int StringStartMask = FlagBitMask;
        public const int StringEndMask = FlagBitMask;
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int ArrayToSequenceEnd(int endIndex) => endIndex | ArrayEndMask;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int MemoryManagerToSequenceStart(int startIndex) => startIndex | MemoryManagerStartMask;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int StringToSequenceStart(int startIndex) => startIndex | StringStartMask;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int StringToSequenceEnd(int endIndex) => endIndex | StringEndMask;
    }
}