Reputation: 9388
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
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
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
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
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.
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