File: StringSegment.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.Primitives\src\Microsoft.Extensions.Primitives.csproj (Microsoft.Extensions.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
 
namespace Microsoft.Extensions.Primitives
{
    /// <summary>
    /// An optimized representation of a substring.
    /// </summary>
    [DebuggerDisplay("{Value}")]
    public readonly struct StringSegment : IEquatable<StringSegment>, IEquatable<string?>
    {
        /// <summary>
        /// A <see cref="StringSegment"/> for <see cref="string.Empty"/>.
        /// </summary>
        public static readonly StringSegment Empty = string.Empty;
 
        /// <summary>
        /// Initializes an instance of the <see cref="StringSegment"/> struct.
        /// </summary>
        /// <param name="buffer">
        /// The original <see cref="string"/>. The <see cref="StringSegment"/> includes the whole <see cref="string"/>.
        /// </param>
        public StringSegment(string? buffer)
        {
            Buffer = buffer;
            Offset = 0;
            Length = buffer?.Length ?? 0;
        }
 
        /// <summary>
        /// Initializes an instance of the <see cref="StringSegment"/> struct.
        /// </summary>
        /// <param name="buffer">The original <see cref="string"/> used as buffer.</param>
        /// <param name="offset">The offset of the segment within the <paramref name="buffer"/>.</param>
        /// <param name="length">The length of the segment.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="buffer"/> is <see langword="null" />.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="offset"/> or <paramref name="length"/> is less than zero, or <paramref name="offset"/> +
        /// <paramref name="length"/> is greater than the number of characters in <paramref name="buffer"/>.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public StringSegment(string buffer, int offset, int length)
        {
            // Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path
            // Negative values discovered though conversion to high values when converted to unsigned
            // Failure should be rare and location determination and message is delegated to failure functions
            if (buffer == null || (uint)offset > (uint)buffer.Length || (uint)length > (uint)(buffer.Length - offset))
            {
                ThrowInvalidArguments(buffer, offset, length);
            }
 
            Buffer = buffer;
            Offset = offset;
            Length = length;
        }
 
        /// <summary>
        /// Gets the <see cref="string"/> buffer for this <see cref="StringSegment"/>.
        /// </summary>
        public string? Buffer { get; }
 
        /// <summary>
        /// Gets the offset within the buffer for this <see cref="StringSegment"/>.
        /// </summary>
        public int Offset { get; }
 
        /// <summary>
        /// Gets the length of this <see cref="StringSegment"/>.
        /// </summary>
        public int Length { get; }
 
        /// <summary>
        /// Gets the value of this segment as a <see cref="string"/>.
        /// </summary>
        public string? Value => HasValue ? Buffer.Substring(Offset, Length) : null;
 
        /// <summary>
        /// Gets whether this <see cref="StringSegment"/> contains a valid value.
        /// </summary>
        [MemberNotNullWhen(true, nameof(Buffer))]
        [MemberNotNullWhen(true, nameof(Value))]
        public bool HasValue => Buffer != null;
 
        /// <summary>
        /// Gets the <see cref="char"/> at a specified position in the current <see cref="StringSegment"/>.
        /// </summary>
        /// <param name="index">The offset into the <see cref="StringSegment"/></param>
        /// <returns>The <see cref="char"/> at a specified position.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="index"/> is greater than or equal to <see cref="Length"/> or less than zero.
        /// </exception>
        public char this[int index]
        {
            get
            {
                if ((uint)index >= (uint)Length)
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
                }
 
                Debug.Assert(Buffer is not null);
                return Buffer[Offset + index];
            }
        }
 
        /// <summary>
        /// Gets a <see cref="ReadOnlySpan{T}"/> from the current <see cref="StringSegment"/>.
        /// </summary>
        /// <returns>The <see cref="ReadOnlySpan{T}"/> from this <see cref="StringSegment"/>.</returns>
        public ReadOnlySpan<char> AsSpan() => Buffer.AsSpan(Offset, Length);
 
        /// <summary>
        /// Gets a <see cref="ReadOnlySpan{T}"/> from the current <see cref="StringSegment"/> that starts
        /// at the position specified by <paramref name="start"/>, and has the remaining length.
        /// </summary>
        /// <param name="start">The zero-based starting character position in this <see cref="StringSegment"/>.</param>
        /// <returns>A <see cref="ReadOnlySpan{T}"/> with the remaining chars that begins at <paramref name="start"/> in
        /// this <see cref="StringSegment"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> is greater than or equal to <see cref="Length"/> or less than zero.
        /// </exception>
        public ReadOnlySpan<char> AsSpan(int start)
        {
            if (!HasValue || start < 0)
            {
                ThrowInvalidArguments(start, Length - start, ExceptionArgument.start);
            }
 
            return Buffer.AsSpan(Offset + start, Length - start);
        }
 
        /// <summary>
        /// Gets a <see cref="ReadOnlySpan{T}"/> from the current <see cref="StringSegment"/> that starts
        /// at the position specified by <paramref name="start"/>, and has the specified <paramref name="length"/>.
        /// </summary>
        /// <param name="start">The zero-based starting character position in this <see cref="StringSegment"/>.</param>
        /// <param name="length">The number of characters in the span.</param>
        /// <returns>A <see cref="ReadOnlySpan{T}"/> with <paramref name="length"/> that begins at
        /// <paramref name="start"/> in this <see cref="StringSegment"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> or <paramref name="length"/> is less than zero, or <paramref name="start"/> + <paramref name="length"/> is
        /// greater than <see cref="Length"/>.
        /// </exception>
        public ReadOnlySpan<char> AsSpan(int start, int length)
        {
            if (!HasValue || start < 0 || length < 0 || (uint)(start + length) > (uint)Length)
            {
                ThrowInvalidArguments(start, length, ExceptionArgument.start);
            }
 
            return Buffer.AsSpan(Offset + start, length);
        }
 
        /// <summary>
        /// Gets a <see cref="ReadOnlyMemory{T}"/> from the current <see cref="StringSegment"/>.
        /// </summary>
        /// <returns>The <see cref="ReadOnlyMemory{T}"/> from this <see cref="StringSegment"/>.</returns>
        public ReadOnlyMemory<char> AsMemory() => Buffer.AsMemory(Offset, Length);
 
        /// <summary>
        /// Compares substrings of two specified <see cref="StringSegment"/> objects using the specified rules,
        /// and returns an integer that indicates their relative position in the sort order.
        /// </summary>
        /// <param name="a">The first <see cref="StringSegment"/> to compare.</param>
        /// <param name="b">The second <see cref="StringSegment"/> to compare.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules for the comparison.</param>
        /// <returns>
        /// A 32-bit signed integer indicating the lexical relationship between the two comparands.
        /// The value is negative if <paramref name="a"/> is less than <paramref name="b"/>, 0 if the two comparands are equal,
        /// and positive if <paramref name="a"/> is greater than <paramref name="b"/>.
        /// </returns>
        public static int Compare(StringSegment a, StringSegment b, StringComparison comparisonType)
        {
            if (a.HasValue && b.HasValue)
            {
                return a.AsSpan().CompareTo(b.AsSpan(), comparisonType);
            }
            else
            {
                CheckStringComparison(comparisonType); // must arg check before returning
                return !a.HasValue ? (b.HasValue ? -1 : 0) : 1; // null sorts less than non-null, and two nulls sort as equal
            }
        }
 
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="obj">An object to compare with this object.</param>
        /// <returns><see langword="true" /> if the current object is equal to <paramref name="obj"/>; otherwise, <see langword="false" />.</returns>
        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            return obj is StringSegment segment && Equals(segment);
        }
 
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns><see langword="true" /> if the current object is equal to <paramref name="other"/>; otherwise, <see langword="false" />.</returns>
        public bool Equals(StringSegment other) => Equals(other, StringComparison.Ordinal);
 
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
        /// <returns><see langword="true" /> if the current object is equal to <paramref name="other"/>; otherwise, <see langword="false" />.</returns>
        public bool Equals(StringSegment other, StringComparison comparisonType)
        {
            if (HasValue && other.HasValue)
            {
                return AsSpan().Equals(other.AsSpan(), comparisonType);
            }
            else
            {
                CheckStringComparison(comparisonType); // must arg check before returning
                return !HasValue && !other.HasValue; // only return true if both are null
            }
        }
 
        // This handles StringSegment.Equals(string, StringSegment, StringComparison) and StringSegment.Equals(StringSegment, string, StringComparison)
        // via the implicit type converter
        /// <summary>
        /// Determines whether two specified <see cref="StringSegment"/> objects have the same value. A parameter specifies the culture, case, and
        /// sort rules used in the comparison.
        /// </summary>
        /// <param name="a">The first <see cref="StringSegment"/> to compare.</param>
        /// <param name="b">The second <see cref="StringSegment"/> to compare.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules for the comparison.</param>
        /// <returns><see langword="true" /> if the objects are equal; otherwise, <see langword="false" />.</returns>
        public static bool Equals(StringSegment a, StringSegment b, StringComparison comparisonType) => a.Equals(b, comparisonType);
 
        /// <summary>
        /// Checks if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>.
        /// </summary>
        /// <param name="text">The <see cref="string"/> to compare with the current <see cref="StringSegment"/>.</param>
        /// <returns><see langword="true" /> if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>; otherwise, <see langword="false" />.</returns>
        public bool Equals(string? text) => Equals(text, StringComparison.Ordinal);
 
        /// <summary>
        /// Checks if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>.
        /// </summary>
        /// <param name="text">The <see cref="string"/> to compare with the current <see cref="StringSegment"/>.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
        /// <returns><see langword="true" /> if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>; otherwise, <see langword="false" />.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(string? text, StringComparison comparisonType)
        {
            if (!HasValue || text == null)
            {
                CheckStringComparison(comparisonType); // must arg check before returning
                return text == Buffer; // only return true if both are null
            }
 
            return AsSpan().Equals(text.AsSpan(), comparisonType);
        }
 
        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override int GetHashCode()
        {
#if NET
            return string.GetHashCode(AsSpan());
#elif (NETSTANDARD2_0 || NETFRAMEWORK)
            // This GetHashCode is expensive since it allocates on every call.
            // However this is required to ensure we retain any behavior (such as hash code randomization) that
            // string.GetHashCode has.
            return Value?.GetHashCode() ?? 0;
#else
#error Target frameworks need to be updated.
#endif
        }
 
        /// <summary>
        /// Checks if two specified <see cref="StringSegment"/> have the same value.
        /// </summary>
        /// <param name="left">The first <see cref="StringSegment"/> to compare, or <see langword="null" />.</param>
        /// <param name="right">The second <see cref="StringSegment"/> to compare, or <see langword="null" />.</param>
        /// <returns><see langword="true" /> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
        public static bool operator ==(StringSegment left, StringSegment right) => left.Equals(right);
 
        /// <summary>
        /// Checks if two specified <see cref="StringSegment"/> have different values.
        /// </summary>
        /// <param name="left">The first <see cref="StringSegment"/> to compare, or <see langword="null" />.</param>
        /// <param name="right">The second <see cref="StringSegment"/> to compare, or <see langword="null" />.</param>
        /// <returns><see langword="true" /> if the value of <paramref name="left"/> is different from the value of <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
        public static bool operator !=(StringSegment left, StringSegment right) => !left.Equals(right);
 
        // PERF: Do NOT add a implicit converter from StringSegment to String. That would negate most of the perf safety.
        /// <summary>
        /// Creates a new <see cref="StringSegment"/> from the given <see cref="string"/>.
        /// </summary>
        /// <param name="value">The <see cref="string"/> to convert to a <see cref="StringSegment"/>.</param>
        public static implicit operator StringSegment(string? value) => new StringSegment(value);
 
        /// <summary>
        /// Creates a see <see cref="ReadOnlySpan{T}"/> from the given <see cref="StringSegment"/>.
        /// </summary>
        /// <param name="segment">The <see cref="StringSegment"/> to convert to a <see cref="ReadOnlySpan{T}"/>.</param>
        public static implicit operator ReadOnlySpan<char>(StringSegment segment) => segment.AsSpan();
 
        /// <summary>
        /// Creates a see <see cref="ReadOnlyMemory{T}"/> from the given <see cref="StringSegment"/>.
        /// </summary>
        /// <param name="segment">The <see cref="StringSegment"/> to convert to a <see cref="ReadOnlyMemory{T}"/>.</param>
        public static implicit operator ReadOnlyMemory<char>(StringSegment segment) => segment.AsMemory();
 
        /// <summary>
        /// Checks if the beginning of this <see cref="StringSegment"/> matches the specified <see cref="string"/> when compared using the specified <paramref name="comparisonType"/>.
        /// </summary>
        /// <param name="text">The <see cref="string"/>to compare.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
        /// <returns><see langword="true" /> if <paramref name="text"/> matches the beginning of this <see cref="StringSegment"/>; otherwise, <see langword="false" />.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <see langword="null" />.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool StartsWith(string text, StringComparison comparisonType)
        {
            if (text == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
            }
 
            if (!HasValue)
            {
                CheckStringComparison(comparisonType); // must arg check before returning
                return false;
            }
 
            return AsSpan().StartsWith(text.AsSpan(), comparisonType);
        }
 
        /// <summary>
        /// Checks if the end of this <see cref="StringSegment"/> matches the specified <see cref="string"/> when compared using the specified <paramref name="comparisonType"/>.
        /// </summary>
        /// <param name="text">The <see cref="string"/>to compare.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
        /// <returns><see langword="true" /> if <paramref name="text"/> matches the end of this <see cref="StringSegment"/>; otherwise, <see langword="false" />.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <see langword="null" />.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool EndsWith(string text, StringComparison comparisonType)
        {
            if (text == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
            }
 
            if (!HasValue)
            {
                CheckStringComparison(comparisonType); // must arg check before returning
                return false;
            }
 
            return AsSpan().EndsWith(text.AsSpan(), comparisonType);
        }
 
        /// <summary>
        /// Retrieves a substring from this <see cref="StringSegment"/>.
        /// The substring starts at the position specified by <paramref name="offset"/> and has the remaining length.
        /// </summary>
        /// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
        /// <returns>A <see cref="string"/> that is equivalent to the substring of remaining length that begins at
        /// <paramref name="offset"/> in this <see cref="StringSegment"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="offset"/> is greater than or equal to <see cref="Length"/> or less than zero.
        /// </exception>
        public string Substring(int offset) => Substring(offset, Length - offset);
 
        /// <summary>
        /// Retrieves a substring from this <see cref="StringSegment"/>.
        /// The substring starts at the position specified by <paramref name="offset"/> and has the specified <paramref name="length"/>.
        /// </summary>
        /// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
        /// <param name="length">The number of characters in the substring.</param>
        /// <returns>A <see cref="string"/> that is equivalent to the substring of <paramref name="length"/> that begins at
        /// <paramref name="offset"/> in this <see cref="StringSegment"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="offset"/> or <paramref name="length"/> is less than zero, or <paramref name="offset"/> + <paramref name="length"/> is
        /// greater than <see cref="Length"/>.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string Substring(int offset, int length)
        {
            if (!HasValue || offset < 0 || length < 0 || (uint)(offset + length) > (uint)Length)
            {
                ThrowInvalidArguments(offset, length, ExceptionArgument.offset);
            }
 
            return Buffer.Substring(Offset + offset, length);
        }
 
        /// <summary>
        /// Retrieves a <see cref="StringSegment"/> that represents a substring from this <see cref="StringSegment"/>.
        /// The <see cref="StringSegment"/> starts at the position specified by <paramref name="offset"/>.
        /// </summary>
        /// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
        /// <returns>A <see cref="StringSegment"/> that begins at <paramref name="offset"/> in this <see cref="StringSegment"/>
        /// whose length is the remainder.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="offset"/> is greater than or equal to <see cref="Length"/> or less than zero.
        /// </exception>
        public StringSegment Subsegment(int offset) => Subsegment(offset, Length - offset);
 
        /// <summary>
        /// Retrieves a <see cref="StringSegment"/> that represents a substring from this <see cref="StringSegment"/>.
        /// The <see cref="StringSegment"/> starts at the position specified by <paramref name="offset"/> and has the specified <paramref name="length"/>.
        /// </summary>
        /// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
        /// <param name="length">The number of characters in the substring.</param>
        /// <returns>A <see cref="StringSegment"/> that is equivalent to the substring of <paramref name="length"/> that begins at <paramref name="offset"/> in this <see cref="StringSegment"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="offset"/> or <paramref name="length"/> is less than zero, or <paramref name="offset"/> + <paramref name="length"/> is
        /// greater than <see cref="Length"/>.
        /// </exception>
        public StringSegment Subsegment(int offset, int length)
        {
            if (!HasValue || offset < 0 || length < 0 || (uint)(offset + length) > (uint)Length)
            {
                ThrowInvalidArguments(offset, length, ExceptionArgument.offset);
            }
 
            return new StringSegment(Buffer, Offset + offset, length);
        }
 
        /// <summary>
        /// Gets the zero-based index of the first occurrence of the character <paramref name="c"/> in this <see cref="StringSegment"/>.
        /// The search starts at <paramref name="start"/> and examines a specified number of <paramref name="count"/> character positions.
        /// </summary>
        /// <param name="c">The Unicode character to seek.</param>
        /// <param name="start">The zero-based index position at which the search starts. </param>
        /// <param name="count">The number of characters to examine.</param>
        /// <returns>The zero-based index position of <paramref name="c"/> from the beginning of the <see cref="StringSegment"/> if that character is found, or -1 if it is not.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> or <paramref name="count"/> is less than zero, or <paramref name="start"/> + <paramref name="count"/> is
        /// greater than <see cref="Length"/>.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int IndexOf(char c, int start, int count)
        {
            int index = -1;
 
            if (HasValue)
            {
                if ((uint)start > (uint)Length)
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
                }
 
                if ((uint)count > (uint)(Length - start))
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
                }
 
                index = AsSpan(start, count).IndexOf(c);
                if (index >= 0)
                {
                    index += start;
                }
            }
 
            return index;
        }
 
        /// <summary>
        /// Gets the zero-based index of the first occurrence of the character <paramref name="c"/> in this <see cref="StringSegment"/>.
        /// The search starts at <paramref name="start"/>.
        /// </summary>
        /// <param name="c">The Unicode character to seek.</param>
        /// <param name="start">The zero-based index position at which the search starts. </param>
        /// <returns>The zero-based index position of <paramref name="c"/> from the beginning of the <see cref="StringSegment"/> if that character is found, or -1 if it is not.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> is greater than or equal to <see cref="Length"/> or less than zero.
        /// </exception>
        public int IndexOf(char c, int start) => IndexOf(c, start, Length - start);
 
        /// <summary>
        /// Gets the zero-based index of the first occurrence of the character <paramref name="c"/> in this <see cref="StringSegment"/>.
        /// </summary>
        /// <param name="c">The Unicode character to seek.</param>
        /// <returns>The zero-based index position of <paramref name="c"/> from the beginning of the <see cref="StringSegment"/> if that character is found, or -1 if it is not.</returns>
        public int IndexOf(char c) => IndexOf(c, 0, Length);
 
        /// <summary>
        /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array
        /// of Unicode characters. The search starts at a specified character position and examines a specified number
        /// of character positions.
        /// </summary>
        /// <param name="anyOf">A Unicode character array containing one or more characters to seek.</param>
        /// <param name="startIndex">The search starting position.</param>
        /// <param name="count">The number of character positions to examine.</param>
        /// <returns>The zero-based index position of the first occurrence in this instance where any character in <paramref name="anyOf"/>
        /// was found; -1 if no character in <paramref name="anyOf"/> was found.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="anyOf"/> is <see langword="null" />.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="startIndex"/> or <paramref name="count"/> is less than zero, or <paramref name="startIndex"/> + <paramref name="count"/> is
        /// greater than <see cref="Length"/>.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int IndexOfAny(char[] anyOf, int startIndex, int count)
        {
            int index = -1;
 
            if (HasValue)
            {
                if ((uint)startIndex > (uint)Length)
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
                }
 
                if ((uint)count > (uint)(Length - startIndex))
                {
                    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
                }
 
                index = Buffer.IndexOfAny(anyOf, Offset + startIndex, count);
                if (index != -1)
                {
                    index -= Offset;
                }
            }
 
            return index;
        }
 
        /// <summary>
        /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array
        /// of Unicode characters. The search starts at a specified character position.
        /// </summary>
        /// <param name="anyOf">A Unicode character array containing one or more characters to seek.</param>
        /// <param name="startIndex">The search starting position.</param>
        /// <returns>The zero-based index position of the first occurrence in this instance where any character in <paramref name="anyOf"/>
        /// was found; -1 if no character in <paramref name="anyOf"/> was found.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="startIndex"/> is greater than or equal to <see cref="Length"/> or less than zero.
        /// </exception>
        public int IndexOfAny(char[] anyOf, int startIndex)
        {
            return IndexOfAny(anyOf, startIndex, Length - startIndex);
        }
 
        /// <summary>
        /// Reports the zero-based index of the first occurrence in this instance of any character in a specified array
        /// of Unicode characters.
        /// </summary>
        /// <param name="anyOf">A Unicode character array containing one or more characters to seek.</param>
        /// <returns>The zero-based index position of the first occurrence in this instance where any character in <paramref name="anyOf"/>
        /// was found; -1 if no character in <paramref name="anyOf"/> was found.</returns>
        public int IndexOfAny(char[] anyOf)
        {
            return IndexOfAny(anyOf, 0, Length);
        }
 
        /// <summary>
        /// Reports the zero-based index position of the last occurrence of a specified Unicode character within this instance.
        /// </summary>
        /// <param name="value">The Unicode character to seek.</param>
        /// <returns>The zero-based index position of value if that character is found, or -1 if it is not.</returns>
        public int LastIndexOf(char value)
        {
            return AsSpan().LastIndexOf(value);
        }
 
        /// <summary>
        /// Removes all leading and trailing whitespaces.
        /// </summary>
        /// <returns>The trimmed <see cref="StringSegment"/>.</returns>
        public StringSegment Trim() => TrimStart().TrimEnd();
 
        /// <summary>
        /// Removes all leading whitespaces.
        /// </summary>
        /// <returns>The trimmed <see cref="StringSegment"/>.</returns>
        public StringSegment TrimStart()
        {
            ReadOnlySpan<char> span = AsSpan();
 
            int i;
            for (i = 0; i < span.Length; i++)
            {
                if (!char.IsWhiteSpace(span[i]))
                {
                    break;
                }
            }
 
            return Subsegment(i);
        }
 
        /// <summary>
        /// Removes all trailing whitespaces.
        /// </summary>
        /// <returns>The trimmed <see cref="StringSegment"/>.</returns>
        public StringSegment TrimEnd()
        {
            ReadOnlySpan<char> span = AsSpan();
 
            int i;
            for (i = span.Length - 1; i >= 0; i--)
            {
                if (!char.IsWhiteSpace(span[i]))
                {
                    break;
                }
            }
 
            return Subsegment(0, i + 1);
        }
 
        /// <summary>
        /// Splits a string into <see cref="StringSegment"/>s that are based on the characters in an array.
        /// </summary>
        /// <param name="chars">A character array that delimits the substrings in this string, an empty array that
        /// contains no delimiters, or null.</param>
        /// <returns>An <see cref="StringTokenizer"/> whose elements contain the <see cref="StringSegment"/>s from this instance
        /// that are delimited by one or more characters in <paramref name="chars"/>.</returns>
        public StringTokenizer Split(char[] chars)
        {
            return new StringTokenizer(this, chars);
        }
 
        /// <summary>
        /// Indicates whether the specified <see cref="StringSegment"/> is null or an Empty string.
        /// </summary>
        /// <param name="value">The <see cref="StringSegment"/> to test.</param>
        /// <returns></returns>
        public static bool IsNullOrEmpty(StringSegment value)
        {
            bool res = false;
 
            if (!value.HasValue || value.Length == 0)
            {
                res = true;
            }
 
            return res;
        }
 
        /// <summary>
        /// Returns the <see cref="string"/> represented by this <see cref="StringSegment"/> or <see cref="string.Empty" /> if the <see cref="StringSegment"/> does not contain a value.
        /// </summary>
        /// <returns>The <see cref="string"/> represented by this <see cref="StringSegment"/> or <see cref="string.Empty" /> if the <see cref="StringSegment"/> does not contain a value.</returns>
        public override string ToString()
        {
            return Value ?? string.Empty;
        }
 
        private static void CheckStringComparison(StringComparison comparisonType)
        {
            // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
            if ((uint)comparisonType > (uint)StringComparison.OrdinalIgnoreCase)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.comparisonType);
            }
        }
 
        // Methods that do no return (i.e. throw) are not inlined
        // https://github.com/dotnet/coreclr/pull/6103
        [DoesNotReturn]
        private static void ThrowInvalidArguments(string? buffer, int offset, int length)
        {
            // Only have single throw in method so is marked as "does not return" and isn't inlined to caller
            throw GetInvalidArgumentsException();
 
            Exception GetInvalidArgumentsException()
            {
                if (buffer == null)
                {
                    return ThrowHelper.GetArgumentNullException(ExceptionArgument.buffer);
                }
 
                if (offset < 0)
                {
                    return ThrowHelper.GetArgumentOutOfRangeException(ExceptionArgument.offset);
                }
 
                if (length < 0)
                {
                    return ThrowHelper.GetArgumentOutOfRangeException(ExceptionArgument.length);
                }
 
                return ThrowHelper.GetArgumentException(ExceptionResource.Argument_InvalidOffsetLength);
            }
        }
 
        [DoesNotReturn]
        private void ThrowInvalidArguments(int offset, int length, ExceptionArgument offsetOrStart)
        {
            throw GetInvalidArgumentsException(HasValue);
 
            Exception GetInvalidArgumentsException(bool hasValue)
            {
                if (!hasValue)
                {
                    return ThrowHelper.GetArgumentOutOfRangeException(offsetOrStart);
                }
 
                if (offset < 0)
                {
                    return ThrowHelper.GetArgumentOutOfRangeException(offsetOrStart);
                }
 
                if (length < 0)
                {
                    return ThrowHelper.GetArgumentOutOfRangeException(ExceptionArgument.length);
                }
 
                return ThrowHelper.GetArgumentException(ExceptionResource.Argument_InvalidOffsetLengthStringSegment);
            }
        }
    }
}