张赐荣——一位视障程序员。
赐荣小站: www.prc.cx

張賜榮

张赐荣的技术博客

博客园 首页 新随笔 联系 订阅 管理

使用C#创建联合结构体

问题

想要用C#创建一种数据类型,类似于 C/C++ 中的联合(union)类型。联合类型主要用于互操作场景,其中非托管代码接受或返回一个联合类型。

解决办法

使用一个结构,并用 [StructLayout] 特性修饰它(在构造函数中指定 LayoutKind.Explicit 布局类型)。此外,利用 FieldOffset 特性标记结构中的每个字段。下面的结构定义了一个联合类型,其中可以存储一个带符号数值。

using System.Runtime.InteropServices;
[StructLayoutAttribute(LayoutKind.Explicit)]
struct SignedNumber 
{
	[FieldOffsetAttribute(0)] public sbyte Num1;
	[FieldOffsetAttribute(0)] public short Num2;
	[FieldOffsetAttribute(0)] public int Num3;
	[FieldOffsetAttribute(0)] public long Num4;
	[FieldOffsetAttribute(0)] public float Num5;
	[FieldOffsetAttribute(0)] public double Num6;
}

下一个结构类似于 SignedNumber 结构,不同之处是除了带符号的数值之外,它还可以包含 String 类型。

[StructLayoutAttribute(LayoutKind.Explicit)]
struct SignedNumberWithText 
{
	[FieldOffsetAttribute(0)] public sbyte Num1;
	[FieldOffsetAttribute(0)] public short Num2;
	[FieldOffsetAttribute(0)] public int Num3;
	[FieldOffsetAttribute(0)] public long Num4;
	[FieldOffsetAttribute(0)] public float Num5;
	[FieldOffsetAttribute(0)] public double Num6;
	[FieldOffsetAttribute(16)] public string Text1;
}

若非必须,不建议使用这种类似于union类型的结构体。

讲解

联合类型是一种在 C++ 代码中较为常见的结构类型;不过,有一种方式可以使用 C# 中的结构数据类型来复制其结构。联合 (union)是一种结构,在内存中的特定位置为该结构接受多种类型。例如,SignedNumber 结构是使用 C# 结构创建的一个联合类型的结构。这种结构可以接受任何类型的带符号的数值类型(sbyte 、int 和 long 等),但它只在结构中的同一个位置(同一偏移量)接受这种数字类型。
 由于 StructLayoutAttribute 可以同时应用于结构和类,在创建联合数据类型时也可以使用类。
注意 FieldOffsetAttribute 将值 0 传递给它的构造函数。这表明这个字段距离结构开始处的偏移量为 0 字节。可以将这个特性与 StructLayoutAttribute 结合使用,手动强制指定结构中的字段开始于什么位置(即每个字段在内存中相对于这个结构开始处的偏移量)。FieldOffsetAttribute 只能与设置为 LayoutKind.Explicit 的 StructLayoutAttribute 一起使用。此外,它不能用于结构内的静态成员。
联合类型可能会带来一些问题,因为几种类型实质上是相互叠加在一起的。最大的问题是如何从联合类型结构中提取正确的数据类型。思考一下,如果你选择在 SignedNumber 结构中存储 long 数值类型的值 long.MaxValue ,会发生什么情况。随后,你可能会偶然尝试从这个结构中提取一个 byte 数据类型值。这样操作,你将会只取回这个 long 值中的第一字节。
另一个问题是在正确的偏移位置开始字段。SignedNumberWithText 联合类型在偏移量为 0 的位置叠加了大量带符号的数值数据类型。这个结构中的最后一个字段位于内存中距离这个结构开始处偏移量为 16 字节的位置。如果你意外地把字符串字段 Text1 覆盖在任何其他带符号的数值数据类型之上,在运行时将得到一个异常。基本规则是:允许你把一种值类型叠加在另一种值类型之上,但是不能把一种引用类型叠加于一种值类型之上。如果用以下特性标记 Text1 字段:
[FieldOffsetAttribute(14)]
就会在运行时引发下面这个异常(注意,编译器不会捕获这个问题)。
System.TypeLoadException: 不能从程序集加载类型'SignedNumberWithText', Version=1.0.0.0, Culture=neutral, PublicKeyToken=fe85c3941fbcc4c5' because it contains an object field at offset 14 that is incorrectly aligned or overlapped by a non-object field.
注意:在 C# 中使用复杂的联合类型时,一定要保证正确的偏移量。

参考资料:

posted on 2022-04-07 16:12  张赐荣  阅读(1357)  评论(0编辑  收藏  举报

感谢访问张赐荣的技术分享博客!
博客地址:https://cnblogs.com/netlog/
知乎主页:https://www.zhihu.com/people/tzujung-chang
个人网站:https://prc.cx/