Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add MemoryExtensions.AsSpan(string, Index/Range)
  • Loading branch information
stephentoub committed Feb 28, 2023
commit 9973e569dd51a67a2839ce968a670d32bdd336c8
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ public static partial class MemoryExtensions
public static System.ReadOnlySpan<char> AsSpan(this string? text) { throw null; }
public static System.ReadOnlySpan<char> AsSpan(this string? text, int start) { throw null; }
public static System.ReadOnlySpan<char> AsSpan(this string? text, int start, int length) { throw null; }
public static System.ReadOnlySpan<char> AsSpan(this string? text, System.Index startIndex) { throw null; }
public static System.ReadOnlySpan<char> AsSpan(this string? text, System.Range range) { throw null; }
public static System.Span<T> AsSpan<T>(this System.ArraySegment<T> segment) { throw null; }
public static System.Span<T> AsSpan<T>(this System.ArraySegment<T> segment, System.Index startIndex) { throw null; }
public static System.Span<T> AsSpan<T>(this System.ArraySegment<T> segment, int start) { throw null; }
Expand Down
46 changes: 32 additions & 14 deletions src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,36 +55,46 @@ public static void StringAsSpanNullNonZeroStartAndLength()
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);
}
}
}

Expand All @@ -93,13 +103,21 @@ public static unsafe void AsSpan_StartAndLength(string text, int start, int leng
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]
[MemberData(nameof(TestHelpers.StringSlice3ArgTestOutOfRangeData), MemberType = typeof(TestHelpers))]
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());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ internal void SetRequestLine(string req)

_rawUrl = req[parts[1]];

ReadOnlySpan<char> version = req.AsSpan()[parts[2]];
ReadOnlySpan<char> version = req.AsSpan(parts[2]);
if (version.Length != 8 || !version.StartsWith("HTTP/", StringComparison.Ordinal))
{
_context.ErrorMessage = "Invalid request line (version).";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,58 @@ public static ReadOnlySpan<char> AsSpan(this string? text, int start)
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>
Expand Down