I have a class hierarchy consisting of an abstract base class with a few helper methods and no fields, and a handful of derived classes with a variety of fields. Here is MCRE code describing the classes in question:
namespace MarshalSizes {
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public abstract class BaseClass {
public abstract int MySize { get; }
public int BaseSize { get {
return Marshal.SizeOf(GetType());
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class DerivedClass : BaseClass {
public UInt32 a32bits;
public UInt16 a16bits;
public Byte onebyte;
public Byte twobyte;
public override int MySize {
get {
return Marshal.SizeOf(GetType());
}
}
}
}
This is in a UWP app. I have simple code that generates a popup box thusly:
DerivedClass classThing = new DerivedClass();
Windows.UI.Popups.MessageDialog dialog = new Windows.UI.Popups.MessageDialog(
String.Format("DerivedClass.BaseSize = {0}\nDerivedClass.MySize = {1}",
classThing.BaseSize, classThing.MySize));
await dialog.ShowAsync();
Simple enough. When I run a debug build:
When I run a release build:

This is wreaking havoc in my real project, where everything is carefully designed to be sized in multiples of 32 bytes (has to do with a communication protocol with a device).
What's going on here? Why does the size grow by a byte in a release build?
EDIT: Since it's been asked, here's a somewhat simplified example of how this type gets marshaled, in a method in the ABC:
// Given Byte[] outbuffer of appropriate size
int size = Marshal.SizeOf(GetType());
IntPtr iptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(this, iptr, false);
Marshal.Copy(iptr, outbuffer, 0, size);
Marshal.FreeHGlobal(iptr);
The outbuffer is part of a packet envelope class that includes CRC values generated over the byte array.
The above is for transmission; for reception, code like this is used:
// Given Byte[] buffer of enough inbound bytes
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var packetType = Marshal.PtrToStructure<MyPacketClass>(handle.AddrOfPinnedObject());
handle.Free();
I'm open to suggestions of "better" ways of doing this, but I would still very much like to have the original question here answered.