I tried building a struct (SA
) using [StructLayout(LayoutKind.Explicit)]
, which had a field which is another struct
(SB
).
First: I was surprised I was allowed to declare that other struct without [StructLayout(LayoutKind.Explicit)]
, whereas in SA
, all fields must have [FieldOffset(0)]
, or the compiler will shout. It doesn't make much sense.
Second: it seems that all reference (object
) fields in SB
are moved to the front of SB
.
:)
Note: I'm not intending to use this in production code. I ask this question mainly out of curiosity.
// No object fields in SB
// Gives the following layout (deduced from experimentation with the C# debugger):
// | f0 | f4 and i | f8 and j | f12 and k | f16 |
[StructLayout(LayoutKind.Explicit)]
struct SA {
[FieldOffset(0)] int f0;
[FieldOffset(4)] SB sb;
[FieldOffset(4)] int f4;
[FieldOffset(8)] int f8;
[FieldOffset(12)] int f12;
[FieldOffset(16)] int f16;
}
struct SB { int i; int j; int k; }
// One object field in SB
// Gives the following layout:
// | f0 | f4 and o1 | f8 and i | f12 and j | f16 and k |
// If I add an `object` field after `j` in `SB`, i *have* to convert
// `f4` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.
[StructLayout(LayoutKind.Explicit)]
struct SA {
[FieldOffset(0)] int f0;
[FieldOffset(4)] SB sb;
[FieldOffset(4)] object f4;
[FieldOffset(8)] int f8;
[FieldOffset(12)] int f12;
[FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; int k; }
// Two `object` fields in `SB`
// Gives the following layout:
// | f0 | f4 and o1 | f8 and o2 | f12 and i | f16 and j | k |
// If I add another `object` field after the first one in `SB`, i *have* to convert
// `f8` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.
[StructLayout(LayoutKind.Explicit)]
struct SA {
[FieldOffset(0)] int f0;
[FieldOffset(4)] SB sb;
[FieldOffset(4)] object f4;
[FieldOffset(8)] object f8;
[FieldOffset(12)] int f12;
[FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; object o2; int k; }
Is this a loophole in the compiler's warnings/errors ?
No, nothing wrong with it. Fields are allowed to overlap, this is why LayoutKind.Explicit exists in the first place. It allows declaring the equivalent of a union in unmanaged code, not otherwise supported in C#. You cannot suddenly stop using [FieldOffset] in a structure declaration, the runtime insist that you use it on all members of the struct. Not technically necessary but a simple requirement that avoids wrong assumptions.
it seems that all reference (object) fields in SB are moved
Yes, this is normal. The CLR lays out objects in an undocumented and undiscoverable way. The exact rules it uses are not documented and subject to change. It also won't repeat for different jitters. Layout doesn't become predictable until the object is marshaled, Marshal.StructureToPtr() call or implicitly by the pinvoke marshaller. Which is the only time the exact layout matters. I wrote about the rationale for this behavior in this answer.