.NET behaviour of LayoutKind.explicit for a field which is itself a struct
我尝试使用
首先:我很惊讶我被允许声明没有
- 这是编译器警告/错误中的漏洞吗?
第二:似乎
- 这种行为在任何地方都有描述吗?
- 是否依赖于实现?
-
是否在任何依赖于实现的地方进行了定义?
:)
注意:我不打算在生产代码中使用它。 我主要出于好奇而问这个问题。
实验性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | // 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 ?
不,没有错。允许字段重叠,这就是为什么LayoutKind.Explicit首先存在的原因。它允许在非托管代码中声明联合的等效项,否则C#不支持。您不能突然停止在结构声明中使用[FieldOffset],编译器坚持要求您在结构的所有成员上使用它。从技术上来讲不是必需的,而是一个避免错误假设的简单要求。
it seems that all reference (object) fields in SB are moved
是的,这很正常。 CLR以无证件和无法发现的方式布置对象。它使用的确切规则未记录在案,并可能随时更改。对于不同的抖动也不会重复。直到将对象编组,Marshal.StructureToPtr()调用或通过pinvoke编组器隐式布局后,布局才变得可预测。这是唯一的确切布局问题。我在这个答案中写了这种行为的基本原理。
第一个问题的答案是否定的,编译器的错误报告中没有漏洞或错误。如果您开始进行显式布局,则编译器将假设您知道自己在做什么(在限制之内-参见下文)。您告诉它在一个结构上覆盖另一个结构。编译器不(也不应该)不在乎您要覆盖的结构是否也未明确布局。
如果编译器确实在意,那么您将无法覆盖任何未明确布局的类型,这意味着在一般情况下您无法进行并集。例如,考虑尝试覆盖
1 2 3 4 5 6 7 8 9 10 | [StructLayout(LayoutKind.Explicit)] struct MyUnion { [FieldOffset(0)] public bool IsDate; [FieldOffset(1)] public DateTime dt; [FieldOffset(1)] public long counter; } |
除非明确布置
至于将引用类型放入显式布局的结构中,您的结果将……可能不是您所期望的。考虑一下这个简单的例子:
1 2 3 4 5 6 7 | struct MyUnion { [FieldOffset(0)] public object o1; [FieldOffset(0)] public SomeRefType o2; } |
这在很大程度上违反了类型安全性。如果编译(很可能会编译),当您尝试使用它时,它将死于TypeLoadException。
编译器将在可能的情况下防止您违反类型安全。我不知道编译器是否知道如何处理这些属性并布局结构,还是只是通过生成的MSIL将布局信息传递给运行时。考虑到您的第二个示例,可能是后者。在该示例中,编译器允许使用特定的布局,但运行时使用TypeLoadException进行轰炸。
在[structlayout.explicit引用类型]上进行的Google搜索显示了一些有趣的讨论。例如,请参阅以显式struct?相互覆盖多个CLR参考字段。