Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(1, 0).DontBox());
Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(1, 1).DontBox());
Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(-1, -1).DontBox());
+
+ Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(new Index(1)).DontBox());
+ Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(new Index(0, fromEnd: true)).DontBox());
+
+ Assert.Throws<ArgumentNullException>(() => str.AsSpan(0..1).DontBox());
+ Assert.Throws<ArgumentNullException>(() => str.AsSpan(new Range(new Index(0), new Index(0, fromEnd: true))).DontBox());
+ Assert.Throws<ArgumentNullException>(() => str.AsSpan(new Range(new Index(0, fromEnd: true), new Index(0))).DontBox());
+ Assert.Throws<ArgumentNullException>(() => str.AsSpan(new Range(new Index(0, fromEnd: true), new Index(0, fromEnd: true))).DontBox());
}
[Theory]
[MemberData(nameof(TestHelpers.StringSliceTestData), MemberType = typeof(TestHelpers))]
- public static unsafe void AsSpan_StartAndLength(string text, int start, int length)
+ public static void AsSpan_StartAndLength(string text, int start, int length)
{
- ReadOnlySpan<char> span;
if (start == -1)
{
- start = 0;
- length = text.Length;
- span = text.AsSpan();
+ Validate(text, 0, text.Length, text.AsSpan());
+ Validate(text, 0, text.Length, text.AsSpan(0));
+ Validate(text, 0, text.Length, text.AsSpan(0..^0));
}
else if (length == -1)
{
- length = text.Length - start;
- span = text.AsSpan(start);
+ Validate(text, start, text.Length - start, text.AsSpan(start));
+ Validate(text, start, text.Length - start, text.AsSpan(start..));
}
else
{
- span = text.AsSpan(start, length);
+ Validate(text, start, length, text.AsSpan(start, length));
+ Validate(text, start, length, text.AsSpan(start..(start+length)));
}
- Assert.Equal(length, span.Length);
-
- fixed (char* pText = text)
+ static unsafe void Validate(string text, int start, int length, ReadOnlySpan<char> span)
{
- char* expected = pText + start;
- void* actual = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
- Assert.Equal((IntPtr)expected, (IntPtr)actual);
+ Assert.Equal(length, span.Length);
+ fixed (char* pText = text)
+ {
+ char* expected = pText + start;
+ void* actual = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
+ Assert.Equal((IntPtr)expected, (IntPtr)actual);
+ }
}
}
public static unsafe void AsSpan_2Arg_OutOfRange(string text, int start)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsSpan(start).DontBox());
+ if (start >= 0)
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => text.AsSpan(new Index(start)).DontBox());
+ }
}
[Theory]
public static unsafe void AsSpan_3Arg_OutOfRange(string text, int start, int length)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsSpan(start, length).DontBox());
+ if (start >= 0 && length >= 0 && start + length >= 0)
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => text.AsSpan(start..(start + length)).DontBox());
+ }
}
}
}
return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), text.Length - start);
}
+ /// <summary>Creates a new <see cref="ReadOnlySpan{Char}"/> over a portion of the target string from a specified position to the end of the string.</summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="startIndex">The index at which to begin this slice.</param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> is less than 0 or greater than <paramref name="text"/>.Length.</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> AsSpan(this string? text, Index startIndex)
+ {
+ if (text is null)
+ {
+ if (!startIndex.Equals(Index.Start))
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex);
+ }
+
+ return default;
+ }
+
+ int actualIndex = startIndex.GetOffset(text.Length);
+ if ((uint)actualIndex > (uint)text.Length)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex);
+ }
+
+ return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)actualIndex /* force zero-extension */), text.Length - actualIndex);
+ }
+
+ /// <summary>Creates a new <see cref="ReadOnlySpan{Char}"/> over a portion of a target string using the range start and end indexes.</summary>
+ /// <param name="text">The target string.</param>
+ /// <param name="range">The range which has start and end indexes to use for slicing the string.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="text"/> is null.</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="range"/>'s start or end index is not within the bounds of the string.</exception>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="range"/>'s start index is greater than its end index.</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan<char> AsSpan(this string? text, Range range)
+ {
+ if (text is null)
+ {
+ Index startIndex = range.Start;
+ Index endIndex = range.End;
+
+ if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
+ }
+
+ return default;
+ }
+
+ (int start, int length) = range.GetOffsetAndLength(text.Length);
+ return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), length);
+ }
+
/// <summary>
/// Creates a new readonly span over the portion of the target string.
/// </summary>