16

I have a (ReadOnly)Span<byte> from which I want to decode a string.

Only in .NET Core 2.1 I have the new overload to decode a string from it without needing to copy the bytes:

Encoding.GetString(ReadOnlySpan<byte> bytes);

In .NET Standard 2.0 and .NET 4.6 (which I also want to support), I only have the classic overloads:

Encoding.GetString(byte[] bytes);
Encoding.GetString(byte* bytes, int byteCount);

The first one requires a copy of the bytes into an array which I want to avoid.
The second requires a byte pointer, so I thought about getting one from my span, like

Encoding.GetString(Unsafe.GetPointer<byte>(span.Slice(100)))

...but I failed finding an actual method for that. I tried void* Unsafe.AsPointer<T>(ref T value), but I cannot pass a span to that, and didn't find another method dealing with pointers (and spans).

Is this possible at all, and if yes, how?

Ray
  • 7,940
  • 7
  • 58
  • 90
  • 2
    Enter `[ReadOnly]Span.GetPinnableReference()`. If using C# 7.3, leveraging this is as simple as `fixed (byte* bytes = span)` -- this compiles for .NET 4.5.2, at least, I haven't tested if it will also really *work* (I only have later frameworks installed). – Jeroen Mostert Jan 18 '19 at 14:43
  • In prior C# versions, you can use the return value of `ref GetPinnableReference()` as the argument to `Unsafe.AsPointer`. You need at least C# 7.0 to use `ref` locals. – Jeroen Mostert Jan 18 '19 at 14:49
  • @JeroenMostert This is great, I'm using C# 7.3 and it's working smooth as silk. Do you want to post an answer about it so I can accept it? – Ray Jan 18 '19 at 16:04
  • With the help of ILSpy, I actually found a more convenient syntax for earlier versions as well. Also not tested, but since the pointers returned are identical I'm going to assume it works. – Jeroen Mostert Jan 18 '19 at 16:16

2 Answers2

29

If you have C# 7.3 or later, you can use the extension made to the fixed statement that can use any appropriate GetPinnableReference method on a type (which Span and ReadOnlySpan have):

fixed (byte* bp = bytes) {
    ...
}

As we're dealing with pointers this requires an unsafe context, of course.

C# 7.0 through 7.2 don't have this, but allow the following:

fixed (byte* bp = &bytes.GetPinnableReference()) {
    ...
}
Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
2

Try this:

Span<byte> bytes = ...;
string s = Encoding.UTF8.GetString((byte*)Unsafe.AsPointer(ref bytes.GetPinnableReference()),
    bytes.Length);
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks! I did not see `GetPinnableReference()` anywhere in IntelliSense. However, this did not seem to work for `ReadOnlySpan` (it cannot pass it as ref because it is readonly); but it would work for my initial question (only now I realized I have a `ReadOnlySpan`, sorry!). – Ray Jan 18 '19 at 16:00
  • @RayKoopa: the reason you couldn't see the method, by the way, is because it was [explicitly hidden as feature](https://github.com/dotnet/corefx/issues/18747). This was back when it was called `DangerousGetPinnableReference`; it's since been downgraded to "not actually that dangerous", but the method's still hidden. – Jeroen Mostert Jan 18 '19 at 16:21
  • 1
    The alternative proposed for `ReadOnlySpan` is not correct! Storing it in a variable first and taking the `ref` of that gives you a reference to the local `byte`, *not* the spanned array. Accessing any element beyond the first this way will give garbage. – Jeroen Mostert Jan 18 '19 at 16:25
  • @JeroenMostert I actually remember that "Dangerous" method name. I didn't follow up on the recent changes, thanks for clearing this up. – Ray Jan 18 '19 at 16:28