Skip to content

MemoryMarshal.Cast behavior seems to be different than MemoryMarshal.Read/Write #28969

@tdecroyere

Description

@tdecroyere

Hello everyone,

I'm currently writing a 3D engine in .net core 3.0 preview3.
In the engine, I'm storing array of structs to custom allocated managed byte arrays.

To do that, I'm using MemoryMarshal Write/Read methods to store or read a struct item.
In some cases, I need to be able to directly take a Span and cast it to a Span of the type one of my structs to avoid copying data.

I have noticed that the cast method seems to compute the size of the struct differently from the read/write methods. For example, I have no issues with structs that contains 4 bytes values like a Int32 but I have issues with structs containing one or more boolean values.

I suppose that there are some alignment rules that are interpreted differently.
I don't know if it is by design, but I think it feels weird that functions from the MemoryMarshal have different behaviors.

I have created a small sample to reproduce my issue because code speaks more than explanations. :)

Thanks for your help!

using System;
using System.Buffers;
using System.Runtime.InteropServices;

namespace MemoryMarshalCastSample
{
    struct IntStruct
    {
        public int TestValue1;
        public int TestValue2; 
    }

    struct BoolStruct
    {
        public bool TestValue1;
        public bool TestValue2;
    }

    /*
        Here is the output of the following sample code:
    
        IntStruct Size in bytes: 8
        BoolStruct Size in bytes: 8

        MemoryMarshal.Read IntStructValue: 3 - 5
        MemoryMarshal.Read IntStructValue: 56 - 89
        MemoryMarshal.Read IntStructValue: 45 - 3
        MemoryMarshal.Read IntStructValue: 343 - 223
        MemoryMarshal.Read IntStructValue: 654 - 95
        MemoryMarshal.Cast IntStructValue: 3 - 5
        MemoryMarshal.Cast IntStructValue: 56 - 89
        MemoryMarshal.Cast IntStructValue: 45 - 3
        MemoryMarshal.Cast IntStructValue: 343 - 223
        MemoryMarshal.Cast IntStructValue: 654 - 95
        
        MemoryMarshal.Read BoolStructValue: True - False
        MemoryMarshal.Read BoolStructValue: False - True
        MemoryMarshal.Read BoolStructValue: True - True
        MemoryMarshal.Read BoolStructValue: False - False
        MemoryMarshal.Read BoolStructValue: False - True
        MemoryMarshal.Cast BoolStructValue: True - False
        MemoryMarshal.Cast BoolStructValue: False - False
        MemoryMarshal.Cast BoolStructValue: False - False
        MemoryMarshal.Cast BoolStructValue: False - False
        MemoryMarshal.Cast BoolStructValue: False - True

    */
    class Program
    {
        static void Main(string[] args)
        {
            // Fill in sample values
            var intStructArray = new IntStruct[]
            {
                new IntStruct() { TestValue1 = 3, TestValue2 = 5 },
                new IntStruct() { TestValue1 = 56, TestValue2 = 89 },
                new IntStruct() { TestValue1 = 45, TestValue2 = 3 },
                new IntStruct() { TestValue1 = 343, TestValue2 = 223 },
                new IntStruct() { TestValue1 = 654, TestValue2 = 95 }
            };

            var boolStructArray = new BoolStruct[]
            {
                new BoolStruct() { TestValue1 = true, TestValue2 = false },
                new BoolStruct() { TestValue1 = false, TestValue2 = true },
                new BoolStruct() { TestValue1 = true, TestValue2 = true },
                new BoolStruct() { TestValue1 = false, TestValue2 = false },
                new BoolStruct() { TestValue1 = false, TestValue2 = true }
            };

            var intStructSizeInBytes = Marshal.SizeOf<IntStruct>();
            var boolStructSizeInBytes = Marshal.SizeOf<BoolStruct>();

            Console.WriteLine($"IntStruct Size in bytes: {intStructSizeInBytes}"); // Display 8
            Console.WriteLine($"BoolStruct Size in bytes: {boolStructSizeInBytes}"); // Display 8

            // Create a byte buffer, it is a temporary buffer just for the sample
            var buffer = ArrayPool<byte>.Shared.Rent(1024);

            // Get spans from the buffer at hardcoded location in this sample
            var intStructSpan = buffer.AsSpan(0);
            var boolStructSpan = buffer.AsSpan(512);

            for (var i = 0; i < intStructArray.Length; i++)
            {
                var offset = i * Marshal.SizeOf<IntStruct>();
                MemoryMarshal.Write(intStructSpan.Slice(offset), ref intStructArray[i]);
            }

            for (var i = 0; i < intStructArray.Length; i++)
            {
                var offset = i * Marshal.SizeOf<IntStruct>();
                var value = MemoryMarshal.Read<IntStruct>(intStructSpan.Slice(offset));
                Console.WriteLine($"MemoryMarshal.Read IntStructValue: {value.TestValue1} - {value.TestValue2}");
            }

            var castedIntStructSpan = MemoryMarshal.Cast<byte, IntStruct>(intStructSpan);

            for (var i = 0; i < intStructArray.Length; i++)
            {
                var value = castedIntStructSpan[i];
                Console.WriteLine($"MemoryMarshal.Cast IntStructValue: {value.TestValue1} - {value.TestValue2}");
            }

            for (var i = 0; i < boolStructArray.Length; i++)
            {
                var offset = i * Marshal.SizeOf(typeof(BoolStruct));
                MemoryMarshal.Write(boolStructSpan.Slice(offset), ref boolStructArray[i]);
            }

            for (var i = 0; i < boolStructArray.Length; i++)
            {
                var offset = i * Marshal.SizeOf(typeof(BoolStruct));
                var value = MemoryMarshal.Read<BoolStruct>(boolStructSpan.Slice(offset));
                Console.WriteLine($"MemoryMarshal.Read BoolStructValue: {value.TestValue1} - {value.TestValue2}");
            }

            var castedBoolStructSpan = MemoryMarshal.Cast<byte, BoolStruct>(boolStructSpan);

            for (var i = 0; i < boolStructArray.Length; i++)
            {
                var value = castedBoolStructSpan[i];
                Console.WriteLine($"MemoryMarshal.Cast BoolStructValue: {value.TestValue1} - {value.TestValue2}");
            }

            ArrayPool<byte>.Shared.Return(buffer, true);
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions