Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a ReadOnlySpan<byte> from a readonly struct?

Tags:

c#

.net-core

The following code causes the compiler to throw error CS1605 ("Cannot pass 'var' as a ref or out argument because it is read-only") in the first line of the property getter.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MyStruct
{
    public readonly int Field1;
    public readonly int Field2;

    public MyStruct(int field1, int field2) => (Field1, Field2) = (field1, field2);

    public ReadOnlySpan<byte> Span
    {
        get
        {
            // This code only works when MyStruct is not read only
            ReadOnlySpan<MyStruct> temp = MemoryMarshal.CreateReadOnlySpan(ref this, 1);
            return MemoryMarshal.Cast<MyStruct, byte>(temp);
        }
    }
}

Removing the readonly from the public readonly struct MyStruct line makes the code work, but for performance reasons, I would really like the struct to be readonly. It makes the code so much more cleaner than having to pass the struct as ref all the time.

Is there a way to get a ReadOnlySpan<byte> from a readonly struct?

Edit: ... without creating an implicit or explicit copy of the structure?

like image 331
Stefan Schultze Avatar asked Nov 24 '19 17:11

Stefan Schultze


1 Answers

It looks like this works:

// The following code will work from C# 7.3 and up, no unsafe keyword required
Span<MyStruct> span = stackalloc MyStruct[1];
span[0] = new MyStruct(3, 4);
var bytes = MemoryMarshal.Cast<MyStruct, byte>(span);

If we wanted to expose it as a property, we could try the following:

// Will not work at runtime
public ReadOnlySpan<byte> Span
{
    get
    {
        unsafe
        {
            fixed (MyStruct* ptr = &this)
            {
                return new ReadOnlySpan<byte>(ptr, sizeof(MyStruct)); // If on the heap, we're doomed as returning will unpin the memory.
            }
        }
    }
}

And marking the struct as a readonly ref struct, this guards us again the struct ever being on the heap. This compiles, but doesn't run as you get a AccessViolationException at runtime. I will do some more digging to see if it's possible, it should be logically safe to do, but may not be possible today.

Another compromise solution is to keep it as a readonly struct (not ref struct) and add this static method:

public static unsafe ReadOnlySpan<byte> GetSpan(ref MyStruct myStruct)
{
    return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref myStruct), sizeof(MyStruct));
}

Then from calling code:

var myStruct = new MyStruct(1, 2);
var span = MyStruct.GetSpan(ref myStruct);

We can improve the usage of this by moving it out into a ref extensions methods (A C# 7.2 feature):

class Program
{
    static void Main()
    {
        var myStruct = new MyStruct(1, 2);
        var span = myStruct.GetSpan();
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MyStruct
{
    public readonly int Field1;
    public readonly int Field2;

    public MyStruct(int field1, int field2) => (Field1, Field2) = (field1, field2);
}

public static class MyStructExtensions
{
    public static unsafe ReadOnlySpan<byte> GetSpan(ref this MyStruct myStruct)
    {
        return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref myStruct), sizeof(MyStruct));
    }
}
like image 161
Stuart Avatar answered Oct 31 '22 03:10

Stuart