-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
In the Framework, there are many types that can be converted into Span<T>, ReadOnlySpan, Memory<T>, and ReadOnlyMemory<T> (a.k.a. slice types).
The conversions are implemented as:
- constructors of slice types, e.g.
Span<T>ctor takingT[]parameter - cast operators from source type to slice type, e.g.
implicit operator Memory<T> (T[] array) As<slice_type>extensions methods, e.g.static ReadOnlySpan<char> AsReadOnlySpan(this string text)
Unfortunately, the set of these conversions is not very consistent. For example, AsReadOnlySpan method converting strings to ReadOnlySpan<char> has three overloads:
public static System.ReadOnlySpan<char> AsReadOnlySpan(this string text);
public static System.ReadOnlySpan<char> AsReadOnlySpan(this string text, int start);
public static System.ReadOnlySpan<char> AsReadOnlySpan(this string text, int start, int length);But logically equivalent T[] to Span<T> conversion method has only one overload:
public static System.Span<T> AsSpan<T>(this T[] array) { throw null; }This means string can be sliced using the following code:
var slice = str.AsReadOnlySpan(5, 10);... but to slice an array, developers have to write:
var slice = arr.AsSpan().Slice(5, 10);This proposal outlines guidelines we could adopt to make the APIs more consistent and easier to use:
- Always provide three overloads of As<slice_type> extension or instance methods. Such extension method will be the main conversion API (i.e. other conversions like ctors and casts are just icing on the cake).
public static <slice_type> As<slice_type>(this <source_type> value) ;
public static <slice_type> As<slice_type>(this <source_type> value, int start);
public static <slice_type> As<slice_type>(this <source_type> value, int start, int length);- If a type can be converted to both heapable slices and by-ref slices, provide extension methods for both.
public static Span<T> AsSpan(this T[] value) ;
public static Span<T> AsSpan(this T[] value, int start);
public static Span<T> AsSpan(this T[] value, int start, int length);
public static Memory<T> AsMemory(this T[] value) ;
public static Memory<T> AsMemory(this T[] value, int start);
public static Memory<T> AsMemory(this T[] value, int start, int length);-
Do not provide conversions to both read-only and read/write slice types. e.g T[] will have conversions to only ``Span
andMemory```, not to ```ReadOnlySpan``` or ```ReadOnlyMemory```. Slice types provide cast from r/w versions to read-only versions. -
Do provide implicit casts between types if appropriate.
Not sure if we want, but we should discuss:
-
Limit constructors to the longest (most flexible) overload.
-
Provide ```AsReadOnly()`` methods on r/w/ slice types.
-
Skip
ReadOnlyfrom conversion method names if the source type is already read-only. For example, string can be converted only toReadOnlySpan<char>and so theAs<slice_type>method should be calledAsSpan, notAsReadOnlySpan.
The resulting changes are:
public static partial class MemoryExtensions
{
// * ADD *
// String
public static System.ReadOnlyMemory<char> AsMemory(this string text) { throw null; }
public static System.ReadOnlyMemory<char> AsMemory(this string text, int start) { throw null; }
public static System.ReadOnlyMemory<char> AsMemory(this string text, int start, int length) { throw null; }
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; }
// T[]
public static System.Memory<T> AsMemory(this T[] array) { throw null; }
public static System.Memory<T> AsMemory(this T[] array, int start) { throw null; }
public static System.Memory<T> AsMemory(tthis T[] array, int start, int length) { throw null; }
public static System.Span<T> AsSpan(this T[] array) { throw null; } // this already exist
public static System.Span<T> AsSpan(this T[] array, int start) { throw null; }
public static System.Span<T> AsSpan(this T[] array, int start, int length) { throw null; }
// ArraySegment
public static System.Memory<T> AsMemory(this ArraySegment<T> segment) { throw null; }
public static System.Memory<T> AsMemory(this ArraySegment<T> segment, int start) { throw null; }
public static System.Memory<T> AsMemory(this ArraySegment<T> segment, int start, int length) { throw null; }
public static System.Span<T> AsSpan(this ArraySegment<T> segment) { throw null; } // this already exist, just rename the parameter
public static System.Span<T> AsSpan(this ArraySegment<T> segment, int start) { throw null; }
public static System.Span<T> AsSpan(this ArraySegment<T> segment, int start, int length) { throw null; }
// * REMOVE *
// these actually just get renamed to AsMemory/Span (see above)
public static System.ReadOnlyMemory<char> AsReadOnlyMemory(this string text) { throw null; }
public static System.ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start) { throw null; }
public static System.ReadOnlyMemory<char> AsReadOnlyMemory(this string text, int start, int length) { throw null; }
public static System.ReadOnlySpan<char> AsReadOnlySpan(this string text) { throw null; }
public static System.ReadOnlySpan<char> AsReadOnlySpan(this string text, int start) { throw null; }
public static System.ReadOnlySpan<char> AsReadOnlySpan(this string text, int start, int length) { throw null; }
// remove and simply use the casts, e.g. from Memory<T> to ReadOnlyMemory<T> instead
public static System.ReadOnlyMemory<T> AsReadOnlyMemory<T>(this System.Memory<T> memory) { throw null; }
public static System.ReadOnlySpan<T> AsReadOnlySpan<T>(this System.Span<T> span) { throw null; }
public static System.ReadOnlySpan<T> AsReadOnlySpan<T>(this System.ArraySegment<T> arraySegment) { throw null; }
public static System.ReadOnlySpan<T> AsReadOnlySpan<T>(this T[] array) { throw null; }
}