| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using System.Diagnostics;
- using System.Globalization;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using Internal.Runtime.CompilerServices;
- namespace System
- {
- /// <summary>
- /// Extension methods for Span{T}, Memory{T}, and friends.
- /// </summary>
- public static partial class MemoryExtensions
- {
- /// <summary>
- /// Returns a value indicating whether the specified <paramref name="value"/> occurs within the <paramref name="span"/>.
- /// <param name="span">The source span.</param>
- /// <param name="value">The value to seek within the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
- /// </summary>
- public static bool Contains(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- return (IndexOf(span, value, comparisonType) >= 0);
- }
- /// <summary>
- /// Determines whether this <paramref name="span"/> and the specified <paramref name="other"/> span have the same characters
- /// when compared using the specified <paramref name="comparisonType"/> option.
- /// <param name="span">The source span.</param>
- /// <param name="other">The value to compare with the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="other"/> are compared.</param>
- /// </summary>
- public static bool Equals(this ReadOnlySpan<char> span, ReadOnlySpan<char> other, StringComparison comparisonType)
- {
- string.CheckStringComparison(comparisonType);
- switch (comparisonType)
- {
- case StringComparison.CurrentCulture:
- return (CultureInfo.CurrentCulture.CompareInfo.CompareOptionNone(span, other) == 0);
- case StringComparison.CurrentCultureIgnoreCase:
- return (CultureInfo.CurrentCulture.CompareInfo.CompareOptionIgnoreCase(span, other) == 0);
- case StringComparison.InvariantCulture:
- return (CompareInfo.Invariant.CompareOptionNone(span, other) == 0);
- case StringComparison.InvariantCultureIgnoreCase:
- return (CompareInfo.Invariant.CompareOptionIgnoreCase(span, other) == 0);
- case StringComparison.Ordinal:
- return EqualsOrdinal(span, other);
- case StringComparison.OrdinalIgnoreCase:
- return EqualsOrdinalIgnoreCase(span, other);
- }
- Debug.Fail("StringComparison outside range");
- return false;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool EqualsOrdinal(this ReadOnlySpan<char> span, ReadOnlySpan<char> value)
- {
- if (span.Length != value.Length)
- return false;
- if (value.Length == 0) // span.Length == value.Length == 0
- return true;
- return span.SequenceEqual(value);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool EqualsOrdinalIgnoreCase(this ReadOnlySpan<char> span, ReadOnlySpan<char> value)
- {
- if (span.Length != value.Length)
- return false;
- if (value.Length == 0) // span.Length == value.Length == 0
- return true;
- return CompareInfo.EqualsOrdinalIgnoreCase(ref MemoryMarshal.GetReference(span), ref MemoryMarshal.GetReference(value), span.Length);
- }
- /// <summary>
- /// Compares the specified <paramref name="span"/> and <paramref name="other"/> using the specified <paramref name="comparisonType"/>,
- /// and returns an integer that indicates their relative position in the sort order.
- /// <param name="span">The source span.</param>
- /// <param name="other">The value to compare with the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="other"/> are compared.</param>
- /// </summary>
- public static int CompareTo(this ReadOnlySpan<char> span, ReadOnlySpan<char> other, StringComparison comparisonType)
- {
- string.CheckStringComparison(comparisonType);
- switch (comparisonType)
- {
- case StringComparison.CurrentCulture:
- return CultureInfo.CurrentCulture.CompareInfo.CompareOptionNone(span, other);
- case StringComparison.CurrentCultureIgnoreCase:
- return CultureInfo.CurrentCulture.CompareInfo.CompareOptionIgnoreCase(span, other);
- case StringComparison.InvariantCulture:
- return CompareInfo.Invariant.CompareOptionNone(span, other);
- case StringComparison.InvariantCultureIgnoreCase:
- return CompareInfo.Invariant.CompareOptionIgnoreCase(span, other);
- case StringComparison.Ordinal:
- if (span.Length == 0 || other.Length == 0)
- return span.Length - other.Length;
- return string.CompareOrdinal(span, other);
- case StringComparison.OrdinalIgnoreCase:
- return CompareInfo.CompareOrdinalIgnoreCase(span, other);
- }
- Debug.Fail("StringComparison outside range");
- return 0;
- }
- /// <summary>
- /// Reports the zero-based index of the first occurrence of the specified <paramref name="value"/> in the current <paramref name="span"/>.
- /// <param name="span">The source span.</param>
- /// <param name="value">The value to seek within the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
- /// </summary>
- public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- string.CheckStringComparison(comparisonType);
- if (value.Length == 0)
- {
- return 0;
- }
- if (span.Length == 0)
- {
- return -1;
- }
- if (comparisonType == StringComparison.Ordinal)
- {
- return SpanHelpers.IndexOf(
- ref MemoryMarshal.GetReference(span),
- span.Length,
- ref MemoryMarshal.GetReference(value),
- value.Length);
- }
- if (GlobalizationMode.Invariant)
- {
- return CompareInfo.InvariantIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
- }
- switch (comparisonType)
- {
- case StringComparison.CurrentCulture:
- case StringComparison.CurrentCultureIgnoreCase:
- return CultureInfo.CurrentCulture.CompareInfo.IndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- case StringComparison.InvariantCulture:
- case StringComparison.InvariantCultureIgnoreCase:
- return CompareInfo.Invariant.IndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- default:
- Debug.Assert(comparisonType == StringComparison.OrdinalIgnoreCase);
- return CompareInfo.Invariant.IndexOfOrdinalIgnoreCase(span, value);
- }
- }
- /// <summary>
- /// Reports the zero-based index of the last occurrence of the specified <paramref name="value"/> in the current <paramref name="span"/>.
- /// <param name="span">The source span.</param>
- /// <param name="value">The value to seek within the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
- /// </summary>
- public static int LastIndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- string.CheckStringComparison(comparisonType);
- if (value.Length == 0)
- {
- return span.Length > 0 ? span.Length - 1 : 0;
- }
- if (span.Length == 0)
- {
- return -1;
- }
- if (GlobalizationMode.Invariant)
- {
- return CompareInfo.InvariantIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None, fromBeginning: false);
- }
- switch (comparisonType)
- {
- case StringComparison.CurrentCulture:
- case StringComparison.CurrentCultureIgnoreCase:
- return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- case StringComparison.InvariantCulture:
- case StringComparison.InvariantCultureIgnoreCase:
- return CompareInfo.Invariant.LastIndexOf(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- default:
- Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
- return CompareInfo.Invariant.LastIndexOfOrdinal(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType) != CompareOptions.None);
- }
- }
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to lowercase,
- /// using the casing rules of the specified culture.
- /// </summary>
- /// <param name="source">The source span.</param>
- /// <param name="destination">The destination span which contains the transformed characters.</param>
- /// <param name="culture">An object that supplies culture-specific casing rules.</param>
- /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
- /// a temporary location before the destination is overwritten.</remarks>
- /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns>
- /// <exception cref="System.ArgumentNullException">
- /// Thrown when <paramref name="culture"/> is null.
- /// </exception>
- public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
- {
- if (culture == null)
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
- if (GlobalizationMode.Invariant)
- TextInfo.ToLowerAsciiInvariant(source, destination);
- else
- culture.TextInfo.ChangeCaseToLower(source, destination);
- return source.Length;
- }
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to lowercase,
- /// using the casing rules of the invariant culture.
- /// </summary>
- /// <param name="source">The source span.</param>
- /// <param name="destination">The destination span which contains the transformed characters.</param>
- /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
- /// a temporary location before the destination is overwritten.</remarks>
- /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns>
- public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
- {
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
- if (GlobalizationMode.Invariant)
- TextInfo.ToLowerAsciiInvariant(source, destination);
- else
- CultureInfo.InvariantCulture.TextInfo.ChangeCaseToLower(source, destination);
- return source.Length;
- }
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to uppercase,
- /// using the casing rules of the specified culture.
- /// </summary>
- /// <param name="source">The source span.</param>
- /// <param name="destination">The destination span which contains the transformed characters.</param>
- /// <param name="culture">An object that supplies culture-specific casing rules.</param>
- /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
- /// a temporary location before the destination is overwritten.</remarks>
- /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns>
- /// <exception cref="System.ArgumentNullException">
- /// Thrown when <paramref name="culture"/> is null.
- /// </exception>
- public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
- {
- if (culture == null)
- ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
- if (GlobalizationMode.Invariant)
- TextInfo.ToUpperAsciiInvariant(source, destination);
- else
- culture.TextInfo.ChangeCaseToUpper(source, destination);
- return source.Length;
- }
- /// <summary>
- /// Copies the characters from the source span into the destination, converting each character to uppercase
- /// using the casing rules of the invariant culture.
- /// </summary>
- /// <param name="source">The source span.</param>
- /// <param name="destination">The destination span which contains the transformed characters.</param>
- /// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
- /// a temporary location before the destination is overwritten.</remarks>
- /// <returns>The number of characters written into the destination span. If the destination is too small, returns -1.</returns>
- public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)
- {
- // Assuming that changing case does not affect length
- if (destination.Length < source.Length)
- return -1;
- if (GlobalizationMode.Invariant)
- TextInfo.ToUpperAsciiInvariant(source, destination);
- else
- CultureInfo.InvariantCulture.TextInfo.ChangeCaseToUpper(source, destination);
- return source.Length;
- }
- /// <summary>
- /// Determines whether the end of the <paramref name="span"/> matches the specified <paramref name="value"/> when compared using the specified <paramref name="comparisonType"/> option.
- /// </summary>
- /// <param name="span">The source span.</param>
- /// <param name="value">The sequence to compare to the end of the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
- public static bool EndsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- string.CheckStringComparison(comparisonType);
- if (value.Length == 0)
- {
- return true;
- }
- if (comparisonType >= StringComparison.Ordinal || GlobalizationMode.Invariant)
- {
- if (string.GetCaseCompareOfComparisonCulture(comparisonType) == CompareOptions.None)
- return span.EndsWith(value);
- return (span.Length >= value.Length) ? (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0) : false;
- }
- if (span.Length == 0)
- {
- return false;
- }
- return (comparisonType >= StringComparison.InvariantCulture) ?
- CompareInfo.Invariant.IsSuffix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)) :
- CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- }
- /// <summary>
- /// Determines whether the beginning of the <paramref name="span"/> matches the specified <paramref name="value"/> when compared using the specified <paramref name="comparisonType"/> option.
- /// </summary>
- /// <param name="span">The source span.</param>
- /// <param name="value">The sequence to compare to the beginning of the source span.</param>
- /// <param name="comparisonType">One of the enumeration values that determines how the <paramref name="span"/> and <paramref name="value"/> are compared.</param>
- public static bool StartsWith(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
- {
- string.CheckStringComparison(comparisonType);
- if (value.Length == 0)
- {
- return true;
- }
- if (comparisonType >= StringComparison.Ordinal || GlobalizationMode.Invariant)
- {
- if (string.GetCaseCompareOfComparisonCulture(comparisonType) == CompareOptions.None)
- return span.StartsWith(value);
- return (span.Length >= value.Length) ? (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0) : false;
- }
- if (span.Length == 0)
- {
- return false;
- }
- return (comparisonType >= StringComparison.InvariantCulture) ?
- CompareInfo.Invariant.IsPrefix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType)) :
- CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, string.GetCaseCompareOfComparisonCulture(comparisonType));
- }
- /// <summary>
- /// Creates a new span over the portion of the target array.
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span<T> AsSpan<T>(this T[] array, int start)
- {
- if (array == null)
- {
- if (start != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException();
- return default;
- }
- if (default(T) == null && array.GetType() != typeof(T[]))
- ThrowHelper.ThrowArrayTypeMismatchException();
- if ((uint)start > (uint)array.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException();
- return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), array.Length - start);
- }
- /// <summary>
- /// Creates a new readonly span over the portion of the target string.
- /// </summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<char> AsSpan(this string text)
- {
- if (text == null)
- return default;
- return new ReadOnlySpan<char>(ref text.GetRawStringData(), text.Length);
- }
- /// <summary>
- /// Creates a new readonly span over the portion of the target string.
- /// </summary>
- /// <param name="text">The target string.</param>
- /// <param name="start">The index at which to begin this slice.</param>
- /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >text.Length).
- /// </exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<char> AsSpan(this string text, int start)
- {
- if (text == null)
- {
- if (start != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
- if ((uint)start > (uint)text.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), text.Length - start);
- }
- /// <summary>
- /// Creates a new readonly span over the portion of the target string.
- /// </summary>
- /// <param name="text">The target string.</param>
- /// <param name="start">The index at which to begin this slice.</param>
- /// <param name="length">The desired length for the slice (exclusive).</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
- /// </exception>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ReadOnlySpan<char> AsSpan(this string text, int start, int length)
- {
- if (text == null)
- {
- if (start != 0 || length != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
- #if BIT64
- // See comment in Span<T>.Slice for how this works.
- if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)text.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- #else
- if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- #endif
- return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), start), length);
- }
- /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
- /// <param name="text">The target string.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- public static ReadOnlyMemory<char> AsMemory(this string text)
- {
- if (text == null)
- return default;
- return new ReadOnlyMemory<char>(text, 0, text.Length);
- }
- /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
- /// <param name="text">The target string.</param>
- /// <param name="start">The index at which to begin this slice.</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >text.Length).
- /// </exception>
- public static ReadOnlyMemory<char> AsMemory(this string text, int start)
- {
- if (text == null)
- {
- if (start != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
- if ((uint)start > (uint)text.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return new ReadOnlyMemory<char>(text, start, text.Length - start);
- }
- /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
- /// <param name="text">The target string.</param>
- /// <param name="start">The index at which to begin this slice.</param>
- /// <param name="length">The desired length for the slice (exclusive).</param>
- /// <remarks>Returns default when <paramref name="text"/> is null.</remarks>
- /// <exception cref="System.ArgumentOutOfRangeException">
- /// Thrown when the specified <paramref name="start"/> index or <paramref name="length"/> is not in range.
- /// </exception>
- public static ReadOnlyMemory<char> AsMemory(this string text, int start, int length)
- {
- if (text == null)
- {
- if (start != 0 || length != 0)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- return default;
- }
- #if BIT64
- // See comment in Span<T>.Slice for how this works.
- if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)text.Length)
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- #else
- if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
- ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
- #endif
- return new ReadOnlyMemory<char>(text, start, length);
- }
- }
- }
|