.NET 的基元类型包括哪些?Unmanaged 和 Blittable 类型又是什么?一文带你深度解析
在讨论.NET 的类型系统的时候,我们经常提到“基元类型(Primitive Type)”的概念,我发现很多人并没有真正理解基元类型就究竟包含哪些(比如很多人觉得字符串是基元类型)。除了明确界定基元类型外,本篇文章还会简单介绍额外两种关于类型的概念——Unmanaged 类型和 Blittable 类型。
一、Primitive Type
二、Unmanaged Type
三、Blittable Type
一、Primitive Type
.NET 下的基元类型(Primitive Type)如下 14 个。我们可以这样来记:长度(字节数)分别为 1、2、4、8 的有/无符号的整数;外加两个基于指针宽度(下 x86=4; x64=8)的整数,计 10 个。长度(字节数)分别为 4 和 8 的单精度和双精度浮点数,计 2 个。外加布尔类型和字符类型, 计 2 个。所以我们熟悉的 String(string)和 Decimal(decimal)并不是基元类型。
整数(10):Byte(byte)/SByte(sbyte), Int16(short)/UInt16(ushort), Int32(int)/UInt32(uint), Int64(long)/UInt64(ulong), IntPtr(nint)/UIntPtr(nuint)
浮点(2):Float(float), Double(double)
布尔(1):Boolean(bool)
字符(1):Char(char)
对于某个指定的 Type 对象,我们可以利用它的 IsPrimitive 属性确定它是否为基元类型。
Type 对象的 IsPrimitive 属性值最终来源于 RuntimeTypeHandle 类型如下这个内部静态方法 IsPrimitive。从该方法的实现和 CorElementType 的枚举成员也可以看出,枚举值 2-13,外加 CorElementType.I(IntPtr)和 CorElementType.U(UIntPtr)这 14 个类型属于基元类型的范畴,这与上面的列表是一致的。
二、Unmanaged Type
顾名思义,Unmanaged 类型可以理解不涉及托管对象引用的值类型。如下的类型属于 Unmanaged 类型的范畴:
14 种基元类型+Decimal(decimal)
枚举类型
指针类型(比如 int*, long*)
只包含 Unmanaged 类型字段的结构体
如果要求泛型类型是一个 Unmananged 类型,我们可以按照如下的方式使用 unmanaged 泛型约束。我在《如何计算一个实例占用多少内存?》提到过,只有 Unmananged 类型采用使用 sizeof 操作符计算大小。
三、Blittable Type
Blittable 是站在基于 P/Invoke 的互操作(InterOp)角度对传递的值是否需要进行转换(Marshaling)而作的分类。Blittable 类型要求在托管内存和非托管内存具有完全一致的表示。如果某个参数为 Blittable 类型,在一个 P/Invoke 方法调用非托管方法的时候,该参数就无需要作任何的转换。与之类似,如果调用方法的返回值是 Blittable 类型,在回到托管世界后也无需转换。如下的类型属于 Blittable 类型范畴:
除 Boolean(bool)和 Char(char)之外的 12 种基元类型,因为布尔值 True 在不同的平台可能会表示成 1 或者-1,对应的字节数可能是 1、2 或者 4,字符涉及不同的编码(Unicode 和 ANSI),所以这两种类型并非 Blittable 类型;
Blittable 基元类型的一维数组;
采用 Sequential 和 Explicitly 布局的且只包含 Blittable 类型成员的结构或者类,因为采用这两种布局的对象最终会按照一种确定的格式转换成对应的 C 风格的结构体。如果采用 Auto 布局,CLR 会按照少占用内存的原则对字段成员重新排序,意味着其内存结构是不确定的。
顺便强调一下,DateTime/DateTimeOffset 都采用 Auto 布局(如下所示),Guid 虽然是一个默认采用 Sequential 布局的结构体,但是最终映射在内存种的字节依赖于字节序(Endianness),所以具有这三种类型字段的结构体或者类都不是 Blittable 类型。
只有 Blittable 类型的实例才能调用 GCHandle 的静态方法 Alloc 为其创建一个 Pinned 类型的 GC 句柄。以如下的代码为例,类 Foobar 的两个属性都是 Blittable 类型,我们通过标注在类型上的 StructLayoutAttribute 将布局类型显式设置成 Sequential 使其称为了一个 Blittable 类型。
如果 Foobar 类定义成如下的形式,都不能使其称为一个 Blittable 类型。前者默认采用 Auto 布局,后者的 Bar 属性并不是 Blittable 类型。如果将这样 Foobar 对象作为参数按照上面的方式调用 GCHandle. Alloc 方法,会直接抛出 ArgumentException 异常,并提示“Object contains non-primitive or non-blittable data. (Parameter 'value')”。
文章转载自:Artech
评论