是的,您可以这样做。您正在走正确的道路,答案在使用BitVector32和FieldOffset以及StructLayout属性中。但是,在执行此操作时,有一些事项需要注意:
- 您需要明确指定将包含所讨论数据的变量的大小。首先要非常重视这一点。例如,在您上面的问题中,您将info指定为unsigned int。unsigned int的大小是多少?32位?64位?这取决于运行此特定版本的.NET的操作系统的版本(可能是.NET Core、Mono或Win32/Win64)。
- 这是什么“尾数”或位顺序?同样,我们可能正在运行任何类型的硬件(考虑Mobile/Xamarin,而不仅仅是笔记本电脑或平板电脑)--因此,您不能假设Intel位顺序。
- 我们将希望避免任何与语言相关的内存管理,或者用C/C++术语来说,是POD(plain old data)类型。这将意味着只使用值类型。
我会根据您的问题和标志0-31的规范做出以下假设:sizeof(int) == 32。
然后的技巧是确保以下内容:
- 所有数据都是按字节对齐的。
- 位字段和info字段在相同的字节边界上对齐。
以下是如何实现这一点的方法:
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct MyUnion
{
#region Lifetime
public MyUnion(int foo)
{
info = new BitVector32(0);
flag1 = BitVector32.CreateSection(1);
flag2 = BitVector32.CreateSection(1, flag1);
flag3 = BitVector32.CreateSection(1, flag2);
}
#endregion
#region Bifield
[FieldOffset(0)]
private BitVector32 info;
#endregion
#region Bitfield sections
private static BitVector32.Section flag1;
private static BitVector32.Section flag2;
private static BitVector32.Section flag3;
#endregion
#region Properties
public bool Flag1
{
get { return info[flag1] != 0; }
set { info[flag1] = value ? 1 : 0; }
}
public bool Flag2
{
get { return info[flag2] != 0; }
set { info[flag2] = value ? 1 : 0; }
}
public bool Flag3
{
get { return info[flag3] != 0; }
set { info[flag3] = value ? 1 : 0; }
}
#endregion
#region ToString
public override string ToString()
{
return $"Name: {nameof(MyUnion)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3} {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
}
#endregion
}
请特别注意构造函数。按照定义,在C#中您无法为结构体定义默认构造函数。但是,我们需要一种方法来确保在使用之前正确地初始化BitVector32对象及其部分。我们通过需要一个带有虚整型参数的构造函数来实现这一点,并像这样初始化对象:
static void Main(string[] args)
{
var myUnion = new MyUnion(0)
{
Flag2 = true
};
顺便提一下,您并不受单个位域的限制——您可以定义任何大小的位域。例如,如果我将您的示例更改为:
union _myUnion
{
unsigned int info;
struct
{
unsigned int flag1 : 3
unsigned int flag2 : 1
unsigned int flag3 : 4
.
.
.
unsigned int flag31 : 1
}
}
我只需要改变我的类为:
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct MyUnion2
{
#region Lifetime
public MyUnion2(int foo)
{
info = new BitVector32(0);
flag1 = BitVector32.CreateSection(0x07);
flag2 = BitVector32.CreateSection(1, flag1);
flag3 = BitVector32.CreateSection(0x0f, flag2);
}
#endregion
#region Bifield
[FieldOffset(0)]
private BitVector32 info;
#endregion
#region Bitfield sections
private static BitVector32.Section flag1;
private static BitVector32.Section flag2;
private static BitVector32.Section flag3;
#endregion
#region Properties
public int Flag1
{
get { return info[flag1]; }
set { info[flag1] = value; }
}
public bool Flag2
{
get { return info[flag2] != 0; }
set { info[flag2] = value ? 1 : 0; }
}
public int Flag3
{
get { return info[flag3]; }
set { info[flag3] = value; }
}
#endregion
#region ToString
public override string ToString()
{
return $"Name: {nameof(MyUnion2)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3} {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
}
#endregion
}
关于这个话题的最后一句话... 如果你绝对需要这样做,那么显然应该这样做。很明显,这需要你对操作系统环境、你运行的语言、你的调用约定以及其他许多脆弱的要求有专门的知识。
在任何其他情况下,这里有很多代码味道,这显然表明它不可移植。但从你的问题的背景中,我推测整个重点是你需要紧密地接近硬件,并且需要这种精度。
买家自负!