-
Notifications
You must be signed in to change notification settings - Fork 5.4k
MemoryMarshal.Cast behavior seems to be different than MemoryMarshal.Read/Write #28969
Description
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);
}
}
}