是的,你可以这样做。您在正确的道路上,答案在于使用BitVector32以及FieldOffset和StructLayout属性。但是,在执行此操作时需要牢记一些事项:
您需要明确指定将包含相关数据的变量的大小。第一项非常重要,需要注意。例如,在您上面的问题中,您将info指定为unsigned int。unsigned int的大小是多少?32位?64位?这取决于运行此特定 .NET 版本的操作系统版本(可能是 .NET Core、Mono 或 Win32/Win64)。
这是什么“字节顺序”或位顺序?同样,我们可能在任何类型的硬件上运行(想想 Mobile/Xamarin,而不仅仅是笔记本电脑或平板电脑)——因此,您不能假设 Intel 位顺序。
我们希望避免任何依赖于语言的内存管理,或者 C/C++ 术语中的 POD(普通旧数据)类型。这意味着只坚持值类型。
我将根据您的问题和标志 0-31 的规范做出sizeof(int) == 32的假设。
诀窍是确保以下内容:
- 所有数据都是字节对齐的。
- 位域和信息域在同一字节边界上对齐。
以下是我们如何做到这一点:
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct MyUnion
{
#region Lifetime
/// <summary>
/// Ctor
/// </summary>
/// <param name="foo"></param>
public MyUnion(int foo)
{
// allocate the bitfield
info = new BitVector32(0);
// initialize bitfield sections
flag1 = BitVector32.CreateSection(1);
flag2 = BitVector32.CreateSection(1, flag1);
flag3 = BitVector32.CreateSection(1, flag2);
}
#endregion
#region Bifield
// Creates and initializes a BitVector32.
[FieldOffset(0)]
private BitVector32 info;
#endregion
#region Bitfield sections
/// <summary>
/// Section - Flag 1
/// </summary>
private static BitVector32.Section flag1;
/// <summary>
/// Section - Flag 2
/// </summary>
private static BitVector32.Section flag2;
/// <summary>
/// Section - Flag 3
/// </summary>
private static BitVector32.Section flag3;
#endregion
#region Properties
/// <summary>
/// Flag 1
/// </summary>
public bool Flag1
{
get { return info[flag1] != 0; }
set { info[flag1] = value ? 1 : 0; }
}
/// <summary>
/// Flag 2
/// </summary>
public bool Flag2
{
get { return info[flag2] != 0; }
set { info[flag2] = value ? 1 : 0; }
}
/// <summary>
/// Flag 1
/// </summary>
public bool Flag3
{
get { return info[flag3] != 0; }
set { info[flag3] = value ? 1 : 0; }
}
#endregion
#region ToString
/// <summary>
/// Allows us to represent this in human readable form
/// </summary>
/// <returns></returns>
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对象及其部分。我们通过需要一个带有虚拟整数参数的构造函数来实现这一点,并像这样初始化对象:
/// <summary>
/// Main entry point
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// brew up one of these...
var myUnion = new MyUnion(0)
{
Flag2 = true
};
顺便说一句,您绝不限于单个位域——您可以定义您喜欢的任何大小的位域。例如,如果我要将您的示例更改为:
union _myUnion
{
unsigned int info;
struct
{
unsigned int flag1 : 3 // bit 0-2
unsigned int flag2 : 1 // bit 3
unsigned int flag3 : 4 // bit 4-7
.
.
.
unsigned int flag31 : 1 // bit 31
}
}
我只想把我的班级改成:
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct MyUnion2
{
#region Lifetime
/// <summary>
/// Ctor
/// </summary>
/// <param name="foo"></param>
public MyUnion2(int foo)
{
// allocate the bitfield
info = new BitVector32(0);
// initialize bitfield sections
flag1 = BitVector32.CreateSection(0x07);
flag2 = BitVector32.CreateSection(1, flag1);
flag3 = BitVector32.CreateSection(0x0f, flag2);
}
#endregion
#region Bifield
// Creates and initializes a BitVector32.
[FieldOffset(0)]
private BitVector32 info;
#endregion
#region Bitfield sections
/// <summary>
/// Section - Flag1
/// </summary>
private static BitVector32.Section flag1;
/// <summary>
/// Section - Flag2
/// </summary>
private static BitVector32.Section flag2;
/// <summary>
/// Section - Flag3
/// </summary>
private static BitVector32.Section flag3;
#endregion
#region Properties
/// <summary>
/// Flag 1
/// </summary>
public int Flag1
{
get { return info[flag1]; }
set { info[flag1] = value; }
}
/// <summary>
/// Flag 2
/// </summary>
public bool Flag2
{
get { return info[flag2] != 0; }
set { info[flag2] = value ? 1 : 0; }
}
/// <summary>
/// Flag 1
/// </summary>
public int Flag3
{
get { return info[flag3]; }
set { info[flag3] = value; }
}
#endregion
#region ToString
/// <summary>
/// Allows us to represent this in human readable form
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Name: {nameof(MyUnion2)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3} {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
}
#endregion
}
关于这个主题的最后一句话......很明显,只有在你绝对必须这样做的情况下才应该这样做。显然,这需要对您的操作系统环境、您运行的语言、您的调用约定以及许多其他脆弱的要求有专门的了解。
在任何其他情况下,这里都有太多代码气味,这显然是不可移植性的尖叫。但是从您的问题的上下文来看,我推测重点是您需要在硬件附近运行并且需要这种精度。
买者自负!