Skip to content

Cleanup after removal of MemoryExtensions As* api. #24938

@KrzysztofCwalina

Description

@KrzysztofCwalina

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:

  1. 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);
  1. 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);
  1. 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.

  2. Do provide implicit casts between types if appropriate.

Not sure if we want, but we should discuss:

  1. Limit constructors to the longest (most flexible) overload.

  2. Provide ```AsReadOnly()`` methods on r/w/ slice types.

  3. 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; }       
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions