Search code examples
c#.netref.net-5

Cast without unsafe between struct and Span<byte> and vice versa in .C# and NET 5


I try to cast a struct to a Span and back (so that the original struct can be changed). That worked partially. How can I cast the Span back into a struct (don't copy) ?

Here is my try. Unfortunately it doesn't work when casting back:

internal struct TestStruct
{
    internal byte ByteValue;
    internal ushort UshortValue;
    internal uint UintValue;
    internal ulong UlongValue;

    internal TestStruct(byte byteValue,ushort ushortValue, uint uintValue, ulong ulongValue) {
        ByteValue = byteValue;
        UshortValue = ushortValue;
        UintValue = uintValue;
        UlongValue = ulongValue;
    }
}

class Program
{
    static void Main(string[] args) {
        TestStruct structInstance = new TestStruct( 0xAA, 0xAAAA, 0xAAAAAAAA, 0xAAAAAAAAAAAAAAAA );            
        var span1 = MemoryMarshal.CreateSpan<TestStruct>(ref structInstance, 1);
        span1[0].ByteValue = 0x11;
        Debug.WriteLine($"span1[0].ByteValue = 0x11 => 0x{structInstance.ByteValue:X2}");
        var byteSpan1 = MemoryMarshal.AsBytes<TestStruct>(span1);
        byteSpan1[0] = 0x22;
        Debug.WriteLine($"byteSpan1[0] = 0x22 => 0x{structInstance.ByteValue:X2}");
        TestStruct structAgain = MemoryMarshal.AsRef<TestStruct>(byteSpan1);
        structAgain.ByteValue = 0xBB;
        Debug.WriteLine($"structAgain.ByteValue = 0xBB => 0x{structInstance.ByteValue:X2}");
    }
}

Result:

span1[0].ByteValue = 0x11 => 0x11
byteSpan1[0] = 0x22 => 0x22
structAgain.ByteValue = 0xBB => 0x22

Obviously MemoryMarshal.AsRef () gives a copy and does not cast. Is there a way to get it working?


Solution

  • The problem you are running into is that struct is a value type. This means, each struct field/parameter/local variable contains the entire value of the struct. Your TestStruct structAgain is an independent copy of your original structInstance. Changes to the copy do not affect the original, and vice versa.

    What you want is a reference to your original struct. To do this, you need the ref keyword. If you look at MemoryMarshal.AsRef, you see the return type is ref T. This means it is returning a reference. You can save the reference by declaring your local variable as ref TestStruct structAgain = ref MemoryMarshal.AsRef<TestStruct>(byteSpan1);. Now structAgain is no longer a copy, but a reference to the original.