如何打包bit fields?寻求非反射实现16位PESHeader方案
嘿,我完全懂你的需求——想用单个16位变量p来打包PESHeader的位域,还不想用反射(毕竟反射在这种场景下不仅慢,代码还乱糟糟的)。下面给你几个比反射高效得多的实现方案,都是实际项目里常用的:
方案1:手动位运算封装(极致性能)
这是性能最优的方案,完全手动控制位掩码,没有任何额外开销,代码逻辑清晰可控。你可以把底层存储封装在结构体或类里,通过属性的get/set来实现位域的读写:
public struct PESHeader { private ushort _p; // 核心16位存储变量 // 示例:Sync位(1位,最高位,掩码0x8000) public bool SyncBit { get => (_p & 0x8000) != 0; set { if (value) _p |= 0x8000; else _p &= (ushort)~0x8000; } } // 示例:传输错误指示位(1位,掩码0x4000) public bool TransportErrorIndicator { get => (_p & 0x4000) != 0; set { if (value) _p |= 0x4000; else _p &= (ushort)~0x4000; } } // 示例:PID字段(12位,低12位,掩码0x0FFF) public ushort PID { get => (ushort)(_p & 0x0FFF); set => _p = (ushort)((_p & 0xF000) | (value & 0x0FFF)); } // 获取原始16位值 public ushort RawValue => _p; // 从原始值初始化 public PESHeader(ushort rawValue) => _p = rawValue; }
优势:没有依赖,性能拉满,每个位域的操作都完全透明,适合对性能要求高的实时场景。
方案2:使用.NET自带的BitVector16(简洁易维护)
如果你不想手动写一堆位掩码,可以用System.Collections.Specialized里的BitVector16——它专门为16位位域设计,封装了单个位的读写逻辑,代码会更简洁:
using System.Collections.Specialized; public class PESHeader { private readonly BitVector16 _bitVector; // 自动生成单个位的掩码 private static readonly int SyncBitMask = BitVector16.CreateMask(); private static readonly int TransportErrorIndicatorMask = BitVector16.CreateMask(SyncBitMask); private static readonly int PayloadStartIndicatorMask = BitVector16.CreateMask(TransportErrorIndicatorMask); // 多比特字段还是需要手动定义掩码 private const ushort PIDMask = 0x0FFF; public PESHeader(ushort rawValue) { _bitVector = new BitVector16(rawValue); } public bool SyncBit { get => _bitVector[SyncBitMask]; set => _bitVector[SyncBitMask] = value; } public bool TransportErrorIndicator { get => _bitVector[TransportErrorIndicatorMask]; set => _bitVector[TransportErrorIndicatorMask] = value; } public ushort PID { get => (ushort)(_bitVector.Data & PIDMask); set => _bitVector.Data = (ushort)((_bitVector.Data & ~PIDMask) | (value & PIDMask)); } public ushort RawValue => _bitVector.Data; }
优势:单个位的操作不用自己写掩码,代码更简洁,可读性更好,性能比反射强N倍,只是比手动位运算略逊一筹。
方案3:StructLayout内存重叠(适合非托管交互)
如果你的PESHeader需要和C/C++等非托管代码交互,或者直接操作内存,可以用StructLayout和FieldOffset实现内存重叠,类似C语言的位域写法:
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Explicit, Size = 2)] // 强制结构体大小为2字节(16位) public struct PESHeader { [FieldOffset(0)] public ushort RawValue; // 整个16位存储变量 [FieldOffset(0)] public byte HighByte; // 高8位字节 [FieldOffset(1)] public byte LowByte; // 低8位字节 // Sync位(高字节的第7位) public bool SyncBit { get => (HighByte & 0x80) != 0; set { if (value) HighByte |= 0x80; else HighByte &= 0x7F; } } // 传输错误指示位(高字节的第6位) public bool TransportErrorIndicator { get => (HighByte & 0x40) != 0; set { if (value) HighByte |= 0x40; else HighByte &= 0xBF; } } // PID字段(高字节低4位 + 低字节全部8位,共12位) public ushort PID { get => (ushort)(((HighByte & 0x0F) << 8) | LowByte); set { HighByte = (byte)((HighByte & 0xF0) | ((value >> 8) & 0x0F)); LowByte = (byte)(value & 0xFF); } } }
优势:可以直接和非托管代码的结构体映射,内存布局完全可控,性能和手动位运算差不多。
这三个方案都完全避开了反射,性能和可维护性都比反射好太多。根据你的场景选就行:追求最快速度就用手动位运算;想少写重复代码就用BitVector16;如果要和非托管代码交互,StructLayout的方式最适配。
内容的提问来源于stack exchange,提问作者electro103
相关产品推荐
相关产品推荐

