十四:CLR如何控制类型字段的布局
CLR能按照它所选择的任何方式来排列类型的字段,可以指示是保持开发人员指定的顺序还是根据需要来重新排列,其目的是为了提高性能。
在定义类或结构上应用System.Runtime.InteropServices.StructLayoutAttribute属性来指定CLR使用何种方式来排列。
LayoutKind.Auto:CLR自动排列字段
LayoutKind.Sequential:开发人员排列的字段
LayoutKind.Explicit:使用偏移量在内存中显示的排列字段的顺序
如果我们不指定它的属性,编译器会选择它默认的属性。
C#编译器为引用类型选择LayoutKind.Auto为它的默认属性,为值类型选择LayoutKind.Sequential,因为结构会经常用于与非托管代码进行互操作,字段必须保持开发人员定义的顺序,但如果创建的值类型不会与非托管代码互操作,则应该覆盖C#编译器的默认设定。如下:
using System;
using System.Runtime.InteropServices;
//该结构不与非托管代码互操作,所以覆盖C#编译器的默认属性,让它为自动排列,提高性能
[StructLayout(LayoutKind.Auto)]
internal struct SomeValType
{
Byte b;
Int32 x;
}
StructLayoutAttribute还允许我们显式指定每个字段的偏移量,这要求向其构造器传递LayoutKind.Explicit,然后向每个字段应用System.Runtime.InteropServices.FieldOffsetAttribute属性的一个实例,并向这个属性构造器传入一个Int32的值,指出字段第一个字节距离实例起始处的偏移量(以字节为单位),显式布局通常用于非托管中的一个union(联合行为),因为可能有多个字段起始于内存中的一个偏移位置。Unino是一种特殊的类,一个union中的数据成员在内存中的存储是互相重叠的,每个数据成员都从相同的内存地址开始,分配给union的存储区数量是“要包含它最大的数据成员”所需的内存数。同一时刻只有一个成员可以被赋给一下值。如下:
using System;
using System.Runtime.InteropServices;
//开发人员显示的排列这个值类型的字段
[StructLayout(LayoutKind.Explicit)]
internal struct SomeValType
{
[FieldOffset(0)]
Byte b;
[FieldOffset(0)]
Int32 x; //b和x字段在这个类型的实例中相互重叠
}
在一个类型中,一个引用类型和一个值类型相互重叠是非法的。在一个类型中,虽然允许多个引用类型在同一个起始偏移位置处相互重叠,但这是无法验证的。如果定义一个类型,并让其中的多个值类型相互重叠,则是合法的,但重叠的字段必须是公共的,如果一个公共的,一个是私有的,则无法通过验证。