16

How can a Span<byte> view (reinterpret cast) be created from a single struct value with no copying, no allocations, and without the unsafe keyword.

I can currently only accomplish this using the unsafe keyword:

public unsafe Span<byte> AsSpan<T>(in T val) where T : unmanaged
{
    void* valPtr = Unsafe.AsPointer(ref Unsafe.AsRef(val));
    return new Span<byte>(valPtr, Marshal.SizeOf<T>());
}

// Alternatively, slightly easier when using 'ref' instead of 'in'
public unsafe Span<byte> AsSpan<T>(ref T val) where T : unmanaged
{
    void* valPtr = Unsafe.AsPointer(ref val);
    return new Span<byte>(valPtr, Marshal.SizeOf<T>());
}

When dealing with an array rather than a single value - this is easily and safely done using MemoryMarshal.Cast<TTo, TFrom>( ... ), for example:

public Span<byte> AsSpan<T>(Span<T> vals) where T : unmanaged
{
    return MemoryMarshal.Cast<T, byte>(vals);
}

Using netstandard2.0, latest language version C# 7.3, and latest RC packages for System.Memory and System.Runtime.CompilerServices.Unsafe:

<PropertyGroup>
   <TargetFramework>netstandard2.0</TargetFramework>
   <LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
   <PackageReference Include="System.Memory" Version="4.5.0" />
   <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.0" />
</ItemGroup>

Edit: Concerning the replies about memory safety/corruption - the unmanaged generic constraint introduced in C# 7.3 can replace the struct generic constraint and allow this to be done in a memory safe way.

See: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

where T : unmanaged The type argument must not be a reference type and must not contain any reference type members at any level of nesting.

zone117x
  • 995
  • 2
  • 10
  • 18
  • And what will happen with your unsafe versions if I pass reference to a field of some class there, and then instance will be relocated by GC? – Evk May 11 '18 at 08:13
  • That would likely result in memory corruption. Did you expect that I add disclaimers to the post stating that? – zone117x May 12 '18 at 04:18
  • @Evk The new "unmanaged" generic constraint in C# 7.3 can be used to prevent the GC situation you described. – zone117x May 21 '18 at 17:01
  • Thanks, good to know. And I was not expecting a disclaimer, just pointed that out in case you don't realize it's not a general purpose solution. – Evk May 21 '18 at 17:08
  • So it is not possible to achieve this if we have a struct with a reference type field? e.g. a `byte[]` – joe Mar 01 '19 at 08:03

2 Answers2

19

Partial solution:

If targeting netcoreapp rather than netstandard2.0 then there is an API available in netcoreapp2.1 (available for download here as of this comment date).


Usage:

using System.Runtime.InteropServices;

public Span<byte> AsSpan<T>(ref T val) where T : unmanaged
{
    Span<T> valSpan = MemoryMarshal.CreateSpan(ref val, 1);
    return MemoryMarshal.Cast<T, byte>(valSpan);
}

This is not a solution to the question which asks for this capability in netstandard2.0. Nevertheless, this should be helpful to many who stumble across this.

zone117x
  • 995
  • 2
  • 10
  • 18
0

It seems for me both of your unsafe methods can corrupt memory. So they shouldn't be used without proper constraints.

1) If struct was allocated on stack, you should use returned span only deeper in same stack. If you return it to calling method you will get invalid pointer.

2) If struct was in heap GC can move containing reference type. You span can be corrupted any time. Use fixed on container instance.

3) If struct was alocated in native memory there are too many options...

  • I updated the original question to use the memory safe `unmanaged` constraint to address this issue. This top-level answer is no longer relevant and is already discussed in the comments to the top level question. – zone117x Jun 07 '18 at 17:11
  • 1
    JFYI using MemoryMarshal.AsBytes is slightly better than Cast. – Anatoly Zhmur Jun 08 '18 at 06:42
  • @zone117x If a struct is a field of a reference type. It still exists on heap while GC can possibly move it. And you still need to somehow "pin" it, the unmanaged constraint doesn't help in this case right? – joe Mar 01 '19 at 01:37
  • 1
    I just took a look at the CoreFX source code. Even though I do not completely understand what happened. I notice that the `span` use a `ref T` like pointer inside so it can somehow "follow" the GC's relocation of the struct that it refers to. – joe Mar 01 '19 at 06:57