TylerBrinkley
TylerBrinkley

Reputation: 1126

How can I create a Memory<T> from a Span<T>?

I'm trying to overload a parsing method to use a ReadOnlySpan<char> parameter in addition to the string version. The problem is that the implementation uses a Dictionary<string, T> for the parsing logic.

I tried switching it to a Dictionary<ReadOnlySpan<char>, T> but of course that didn't work as ReadOnlySpan<char> isn't allowed as a generic parameter since it's a stack only object. I then switched it to using ReadOnlyMemory<char> which is allowed. I then implemented a basic Ordinal comparer but am now having troubles creating a ReadOnlyMemory<char> from the ReadOnlySpan<char> parameter. Is this possible?

Update

It seems that this is not possible. In order to support the scenario I've posted above I will change the dictionary to have an int key which is the hashcode of the ReadOnlySpan<char> and make the value a list with the string embedded in the element and manually have to resolve hash code collisions.

Upvotes: 18

Views: 14272

Answers (2)

Svek
Svek

Reputation: 12898

TL;DR:

You can go Memory<T> to Span<T>, but not the other way around.

Background:

There is an informative 2018 MSDN Magazine article article that introduces Span<T> in a 2018 MSDN Magazine article

Span<T> instances can only live on the stack, not on the heap. This means you can’t box spans (and thus can’t use Span<T> with existing reflection invoke APIs, for example, as they require boxing). It means you can’t have Span<T> fields in classes, or even in non-ref-like structs. It means you can’t use spans in places where they might implicitly become fields on classes, for instance by capturing them into lambdas or as locals in async methods or iterators (as those “locals” may end up being fields on the compiler-generated state machines.) It also means you can’t use Span<T> as a generic argument, as instances of that type argument could end up getting boxed or otherwise stored to the heap (and there’s currently no “where T : ref struct” constraint available).

...

You can create a Memory<T> from an array and slice it just as you would a span, but it’s a (non-ref-like) struct and can live on the heap. Then, when you want to do synchronous processing, you can get a Span<T> from it, for example:

static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
  int bytesRead = await stream.ReadAsync(buffer);
  return Checksum(buffer.Span.Slice(0, bytesRead));
  // Or buffer.Slice(0, bytesRead).Span
}
static int Checksum(Span<byte> buffer) { ... }

I think his article clears it up better than me writing my own answer.

Upvotes: 19

ZakChar
ZakChar

Reputation: 346

You can go from a Span<T> to a Memory<T> if T is an unmanaged type. But beware that the Memory<T> object you are creating should enforce "manually" all the constraints that C# compiler guarantees on Spans.

// data is Span<T> or ReadOnlySpan<T>
fixed (T* pointer = &data.GetPinnableReference())
{
     using var memoryManager = new UnmanagedMemoryManager<T>(pointer, data.Length);
     var memory = memoryManager.Memory;
     // you must use the memory object inside this fixed block!
}

The UnmanagedMemoryManager can be found in the nuget package Pipelines.Sockets.Unofficial (I don't maintain this package). But you can also code your own implementation of an unmanaged MemoryManager<T> if you prefer to build it differently.

Upvotes: 3

Related Questions