23

I have been reading about span for a while now, and just tried to implement it. However, while I can get span to work I cannot figure out how to get a stream to accept it like they do in the examples. Other examples show int.parse supporting spans as well but I can't find overloads or extensions that make it possible.

I have tried it in both .net standard 2.0 and .net core 2.0

Please point me in the right direction to make this work.

Code example

Span<Byte> buffer = new Span<byte>();
int bytesRead = stream.Read(buffer);
live2
  • 3,771
  • 2
  • 37
  • 46

2 Answers2

16

Span results from streams are supported in .NET Core 2.1. If you check the current source code of eg Stream you'll see it has overloads like Read(Span) that read into a Span<byte> instead of byte[], or Write(ReadOnlySpan) that can write out a ReadOnlySpan<byte> instead of a byte[], overloads that use Memory etc.

To target .NET Core 2.1, you'll have to install at least Visual Studio 2017 15.7 Preview 4 or the latest SDK for .NET Core 2.1

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • I thought it would be something like that. I was just unable to find the links on any of the websites. Thanks for your help. I will probably just wait until it gets out of preview. – Christopher James Woods May 01 '18 at 12:50
  • @ChristopherJamesWoods you don't have to. 2.1 installs side-by-side and you get to choose which runtime version to use when you create a new project. Plus, MS Build is just around the corner. There may be some interesting announcements next week – Panagiotis Kanavos May 02 '18 at 07:32
  • @PanagiotisKanavos What happens when you close and dispose of the stream? Will the `Span` point into the void? – silkfire Oct 19 '19 at 22:29
  • @silkfire the method use spans provided by the caller. They'll exist for as long as the caller needs them. – Panagiotis Kanavos Oct 21 '19 at 09:57
6

Let's look at an example that I have handy, where the Span<T> happens to come from PipeWriter.

var bufferSpan = pipeWriter.GetSpan(count);
stream.Write( /* Darn, I need an array, because no Span<T> overloads outside Core 2.1! */ );

Try to get a Memory<T> instead of Span<T>, for which you can get the underlying array (except under some exotic circumstances).

var bufferMemory = pipeWriter.GetMemory(count); // Instead of GetSpan()
var bufferArraySegment = bufferMemory.GetUnderlyingArray();
stream.Write(bufferArraySegment.Array, bufferArraySegment.Offset, bufferArraySegment.Count); // Yay!

GetUnderlyingArray() here is a small extension method:

/// <summary>
/// Memory extensions for framework versions that do not support the new Memory overloads on various framework methods.
/// </summary>
internal static class MemoryExtensions
{
    public static ArraySegment<byte> GetUnderlyingArray(this Memory<byte> bytes) => GetUnderlyingArray((ReadOnlyMemory<byte>)bytes);
    public static ArraySegment<byte> GetUnderlyingArray(this ReadOnlyMemory<byte> bytes)
    {
        if (!MemoryMarshal.TryGetArray(bytes, out var arraySegment)) throw new NotSupportedException("This Memory does not support exposing the underlying array.");
        return arraySegment;
    }
}

All in all, this lets you use the methods with a 'modern' return type in combination with the 'old' framework overloads - without any copying. :)

Timo
  • 7,992
  • 4
  • 49
  • 67
  • This only works for read-only memory. The truth is that `Stream` API is quite broken: `byte[]` is not a substitute for "a chunk of memory". Internally, the `Read(Span)` copies via an intermediate array. – Kuba hasn't forgotten Monica Sep 05 '19 at 15:26
  • 1
    @KubaOber I think you could never have a `Stream` based on a `(ReadOnly)Span`. Spans are stack-only types, and streams are not. The stream could outlive the span. `Stream` in its current form could be based on `(ReadOnly)Memory` at best. You'd need a stack-only stream if you wanted to wrap a span. – Timo Sep 06 '19 at 09:33
  • Of course Span and Memory go hand-in-hand, and both should be supported (supporting one must imply supporting other, otherwise it's a broken API). – Kuba hasn't forgotten Monica Sep 08 '19 at 15:37
  • Actually I see that I've been mixing things up. The question itself was about reading from a stream into a span, which either requires .NET Core 2.1+, or that you pass your own array and afterwards you get a span over that array (which is probably not what OP was looking for). My answer, I now realize, addresses _writing_ to the stream _from_ a `Memory`, in the absence of the Core 2.1 overloads that accept a span. Thirdly, there is the case of constructing a stream over a `Memory` of data, which does not make sense with spans, because a stack-only stream would be of limited use. – Timo Sep 09 '19 at 07:55