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 taking T[] 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 ``SpanandMemory```, 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 ReadOnly from conversion method names if the source type is already read-only. For example, string can be converted only to ReadOnlySpan<char> and so the As<slice_type> method should be called AsSpan, not AsReadOnlySpan.
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; }
}
In the Framework, there are many types that can be converted into
Span<T>,ReadOnlySpan,Memory<T>, andReadOnlyMemory<T>(a.k.a. slice types).The conversions are implemented as:
Span<T>ctor takingT[]parameterimplicit 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,
AsReadOnlySpanmethod converting strings toReadOnlySpan<char>has three overloads:But logically equivalent
T[]toSpan<T>conversion method has only one overload:This means string can be sliced using the following code:
... but to slice an array, developers have to write:
This proposal outlines guidelines we could adopt to make the APIs more consistent and easier to use:
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: