Skip to content

[API Proposal]: Add Stream wrappers for memory and text-based types #82801

Open
@jozkee

Description

@jozkee

Background and motivation

This is meant to bundle #46663, #27156, and #22838.

There's the usual need of wrapping memory and text-based types in a Stream and given that there are multiple implementations spread across different libraries, it would be ideal to have a standardized API for it directly on .NET.

A few examples of the current options available:
CommunityToolkit.HighPerformance provides AsStream() extension methods for the following:
Memory<byte>
ReadOnlyMemory<byte>
IMemoryOwner<byte>
IBufferWriter<byte>

Nerdbank.Streams also provides AsStream() extensions for the following:
IDuplexPipe
PipeReader/PipeWriter
WebSocket
ReadonlySequence<byte>
IBufferWriter<byte>

While I'm not certain of providing support for all the types listed above, I think a good starting point would be the following based on the evidence shown in the issues I bundled.

API Proposal

Originally proposed by @bartonjs on #82801 (comment).

// It will be in a new assembly with a dependency on System.Memory.dll and System.Runtime.dll
namespace System.IO
{
    public partial class StreamFactory
    {
        public static Stream FromText(string text, Encoding encoding);
        public static Stream FromText(Memory<char> text, Encoding encoding);
        public static Stream FromText(ReadOnlyMemory<char> text, Encoding encoding);

        public static Stream FromData(Memory<byte> data);
        public static Stream FromData(ReadOnlyMemory<byte> data);
        public static Stream FromData(ReadOnlySequence<byte> data);
    }
}

API Usage

Example 1:

static HttpClient client = new HttpClient();
static void SendString(string str)
{
    var request = new HttpRequestMessage(HttpMethod.Post, "contoso.com");
    request.Content = new StreamContent(StreamFactory.FromText(str));
    client.Send(request);
}

Example 2:

static unsafe void DoStreamOverSpan(ReadOnlySpan<byte> span)
{
    fixed (byte* ptr = &MemoryMarshal.GetReference(span))
    {
        using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, span.Length))
        {
            using Stream s = StreamFactory.FromData((ReadOnlyMemory<byte>)manager.Memory);
            // Do something with it...
        }
    }
}

Alternative Design 1

From #82801 (comment).

when two overloads have different semantics the guidelines suggest they shouldn't be overloads, but different names (different method groups).

I had thought this was only about read-only streams. If writing is desired, then perhaps it becomes something more like

public class StreamFactory
{
    public static Stream CreateReadOnly(string text, Encoding encoding);
    public static Stream CreateReadOnly(ReadOnlyMemory<char> text, Encoding encoding);
    public static Stream CreateReadOnly(byte[] data);
    public static Stream CreateReadOnly(ReadOnlyMemory<byte> data);
    public static Stream CreateReadOnly(ReadOnlySequence<byte> data);

    public static Stream CreateWriteOnly(Memory<char> buffer, Encoding encoding);
    public static Stream CreateWriteOnly(byte[] buffer);
    public static Stream CreateWriteOnly(Memory<byte> buffer);

    public static Stream CreateReadWrite(Memory<char> buffer, Encoding encoding);
    public static Stream CreateReadWrite(byte[] buffer);
    public static Stream CreateReadWrite(Memory<byte> buffer);
}

Alternative Design 2

We can think of providing explicit types, e.g: StringStream, instead of simply returning Stream, but I like this way more since the Stream types would be internal and could make it more flexible and maintainable for future improvements e.g: we could start returning a MemoryStream instead of an non-visible one if MemoryStream reaches the point where it finally gets a ReadOnlyMemory<byte> ctor.

Alternative Design 3 (original proposal)

Follows prior-art of libraries listed above, we could provide AsStream() extensions.
Based on comments in this thread pointing out that such extensions don't address a common-enough scenario I've moved this proposal to the alternative section.

namespace System.IO
{
    public static class StreamTextExtensions
    {
        public static Stream AsStream(this ReadOnlyMemory<char> instance, Encoding encoding) { }
        public static Stream AsStream(this string instance, Encoding encoding) { }
    }

    public static class StreamMemoryExtensions
    {
        public static Stream AsStream(this Memory<byte> instance) { }
        public static Stream AsStream(this ReadOnlyMemory<byte> instance) { }
        public static Stream AsStream(this ReadOnlySequence<byte> instance){ }
    }
}

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.IO

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions