Skip to content

Consider adding {ReadOnly}SpanMemoryStream types #22838

@stephentoub

Description

@stephentoub

Just as we have a MemoryStream that works with byte[] and an UnmanagedMemoryStream that works with a byte* and a length, we should support treating Memory<byte> and ReadOnlyMemory<byte> as streams.

It’s possible we could get away with reimplementing MemoryStream on top of Memory<byte>, but more than likely this would introduce regressions for at least some existing use cases, and it doesn't work with ReadOnlyMemory<byte>.

It would be nice if we could potentially write something ala:

Stream s = memory.TryGetArray(out var segment) ?
    new MemoryStream(segment.Array, segment.Offset, segment.Count) :
    new UnmanagedMemoryStream(&memory.DangerousGetPinnableReference(), buffer.Length);

but it's possible that TryGetArray could return false but the wrapped memory not inherently pinned, in which case the UnmanagedMemoryStream case would be wrapping a pointer to memory that could get moved. To pin it and keep track of the pinning, you'd need a wrapper Stream type anyway.

So, we could have two new stream types for these specific types:

namespace System.IO
{
    public class BufferStream : Stream
    {
        public BufferStream(Memory<byte> buffer);
        public BufferStream(Memory<byte> buffer, bool publiclyVisible);
        public bool TryGetBuffer(out Memory<byte> buffer);// overrides of all members on Stream
    }

    public class ReadOnlyBufferStream : Stream
    {
        public ReadOnlyBufferStream(ReadOnlyMemory<byte> buffer);
        public ReadOnlyBufferStream(ReadOnlyMemory<byte> buffer, bool publiclyVisible);
        public bool TryGetBuffer(out ReadOnlyMemory<byte> buffer);// overrides of all members on Stream, with those that write throwing NotSupportedException
    }
}

The name of BufferStream is unfortunately close to that of BufferedStream, and they mean very different things, but I’m not sure that’s important enough to consider a less meaningful name.

Alternatively, we could hide these behind a factory, e.g.

public static Stream ToStream(this Memory<byte> buffer) => new BufferStream(buffer);
public static Stream ToStream(this ReadOnlyMemory<byte> buffer) => new ReadOnlyBufferStream(buffer);

to keep them from needing to be public.

Especially if we do the latter, we would want to consider either adding TryGetBuffer virtuals to the base Stream class, or introducing an interface that could be queried for and which exposing such methods.
Then for example code that uses Streams and has optimizations when working directly with the underlying data can query for the interface and special-case when the underlying Buffer<T> can be accessed, e.g.

namespace System
{
    public interface IHasBuffer<T>
    {
        bool TryGetBuffer(out Memory<T> buffer);
        bool TryGetBuffer(out ReadOnlyMemory<T> buffer);
    }
}

We could implement this not only on BufferStream and ReadOnlyBufferStream, but also on MemoryStream, UnmanagedMemoryStream, and even on non-streams, basically anything that can hand out a representation of its internals as buffers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions