Stefan Schultze
Stefan Schultze

Reputation: 9388

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

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?

Upvotes: 6

Views: 6729

Answers (4)

Hefaistos68
Hefaistos68

Reputation: 428

Just adding a generic solution here, based on the previous answers:

public class StructExtensions
{
    public static unsafe ReadOnlySpan<byte> AsSpan<T>(ref T myStruct) where T : struct
    {
        return MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref myStruct, 1));
    }
}

You do use it like:

var p = StructExtensions.AsSpan<MyStruct>(ref this /* or whatever your variable is */);

Upvotes: 1

Stuart
Stuart

Reputation: 5496

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));
    }
}

Upvotes: 8

Vano Maisuradze
Vano Maisuradze

Reputation: 5899

If you take a look into source code of MemoryMarshal.CreateReadOnlySpan, you can see, that it just creates a new instance of ReadOnlySpan by passing reference and length parameters.

Simple solution would be to create ReadOnlySpan instance yourself by passing array:

ReadOnlySpan<MyStruct> temp = new ReadOnlySpan<MyStruct>(new MyStruct[] { this });

This will use different ctor, but implementation is similar and you will get the same result.

Edit:

Or using unsafe:

public ReadOnlySpan<byte> Span
{
    get
    {
        unsafe
        {

            return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref this), 1);
        }
    }
}

Upvotes: 0

Orace
Orace

Reputation: 8359

Code below is unsafe.

See Stuart comment:

It will be very unsafe to do that, as you don't know where the struct lives (stack vs heap), if you made it a ref struct, you could guarantee it's stack only, and would be safe to do without assuming you have been pinned by some unsafe code upfront.

MemoryMarshal.CreateReadOnlySpan method has a warning too.

And now the code

You can achieve it in two steps.

  • Put your struct in a span<T> where T is the type of your struct.
  • Cast the span<T> to a span<byte>

    var s = new MutableStruct();
    var span = MemoryMarshal.CreateReadOnlySpan(ref s, 1);
    var byteSpan = MemoryMarshal.Cast<MutableStruct, byte>(span);
    
    Console.WriteLine(string.Join("", byteSpan.ToArray().Select(v => $"{v:X2}"))); // 00000000
    
    s.SetX(42);
    
    Console.WriteLine(string.Join("", byteSpan.ToArray().Select(v => $"{v:X2}"))); // 2A0000000
    
    Console.ReadKey();
    
    // here the used struct
    struct MutableStruct
    {
        private int _x;
        public int X => _x;
        public void SetX(int value) => _x = value;
    }
    

You can test it here

Upvotes: 0

Related Questions