Structs have been troublesome beasts in computer engineering for a very long time. Their memory layout is very hardware dependent. To make them efficient, their members must be aligned so the CPU can read and write their values quickly without having to multiplex the bytes to fit the memory bus width. Every compiler has its own strategy of packing the members, often directed by, for example, the #pragma pack directive in a C or C++ program.
Which is okay, but rather a problem in interop scenarios. Where one chunk of code may make different assumptions about the structure layout than another chunk, compiled by a different compiler. You can see this back in COM, .NET's grandfather solution to interop programming. COM has very poor support for handling structs. It doesn't support them as a native automation type but has a workaround through the IRecordInfo interface. Which lets a program discover the memory layout at runtime through an explicit declaration of the structure in a type library. Which works okay, but is quite inefficient.
The .NET designers made a very courageous, and correct, decision to solve this problem. They made the memory layout of a struct completely undiscoverable. There is no documented way to retrieve the offset of a member. And by extension, no way to discover the size of the struct. Everybody's favorite answer, use Marshal.SizeOf() is not in fact the solution. That returns the size of struct after it is marshaled, the size you'd need to pass to, say, Marshal.AllocCoTaskMem() before you call Marshal.StructureToPtr. That arranges and aligns the struct members according to the [StructLayout] attribute that's associated with the struct. Note that this attribute isn't required for structs (like it is for classes), the runtime implements a default one which uses the declared order for the members.
One very nice side-effect of the layout being undiscoverable is that the CLR can play tricks