如何实现类似FieldOffset的BitOffset位偏移特性?
实现自定义位级内存布局的方案
要在C#中实现类似FieldOffset的位级内存布局控制,需要结合自定义特性、强类型位结构体和源代码生成器来完成,以下是具体实现步骤:
1. 定义核心特性与位类型
首先需要定义标记用的特性,以及对应不同位数的强类型位结构体:
标记特性
using System; namespace BitLayout { // 标记需要自动生成位布局逻辑的结构体 [AttributeUsage(AttributeTargets.Struct)] public class BitLayoutAttribute : Attribute { // 指定底层存储类型,默认byte(8位),可选ushort/uint/ulong public Type StorageType { get; set; } = typeof(byte); } // 标记位字段的起始比特偏移 [AttributeUsage(AttributeTargets.Field)] public class BitOffsetAttribute : Attribute { public int Offset { get; } public BitOffsetAttribute(int offset) => Offset = offset; } // 标记位类型的占用位数 [AttributeUsage(AttributeTargets.Struct)] public class BitSizeAttribute : Attribute { public int Size { get; } public BitSizeAttribute(int size) => Size = size; } }
强类型位结构体
针对不同位数定义类型安全的位结构体,包含取值范围校验和隐式转换:
using System; namespace BitLayout { [BitSize(1)] public readonly struct Bit1 { public bool Value { get; } public Bit1(bool value) => Value = value; public static implicit operator bool(Bit1 bit) => bit.Value; public static implicit operator Bit1(bool value) => new Bit1(value); } [BitSize(2)] public readonly struct Bit2 { public int Value { get; } private const int MaxValue = (1 << 2) - 1; public Bit2(int value) { if (value < 0 || value > MaxValue) throw new ArgumentOutOfRangeException(nameof(value), $"Bit2只能存储0-{MaxValue}之间的整数"); Value = value; } public static implicit operator int(Bit2 bit) => bit.Value; public static implicit operator Bit2(int value) => new Bit2(value); } [BitSize(5)] public readonly struct Bit5 { public int Value { get; } private const int MaxValue = (1 << 5) - 1; public Bit5(int value) { if (value < 0 || value > MaxValue) throw new ArgumentOutOfRangeException(nameof(value), $"Bit5只能存储0-{MaxValue}之间的整数"); Value = value; } public static implicit operator int(Bit5 bit) => bit.Value; public static implicit operator Bit5(int value) => new Bit5(value); } }
2. 编写源代码生成器
使用C#源代码生成器,在编译时自动扫描带有BitLayout特性的结构体,生成底层存储和位操作逻辑,避免手动编写繁琐的位运算:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace BitLayout.Generator { [Generator] public class BitLayoutGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new BitLayoutSyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not BitLayoutSyntaxReceiver receiver) return; var bitLayoutAttr = context.Compilation.GetTypeByMetadataName("BitLayout.BitLayoutAttribute"); var bitOffsetAttr = context.Compilation.GetTypeByMetadataName("BitLayout.BitOffsetAttribute"); var bitSizeAttr = context.Compilation.GetTypeByMetadataName("BitLayout.BitSizeAttribute"); if (bitLayoutAttr == null || bitOffsetAttr == null || bitSizeAttr == null) return; foreach (var structDeclaration in receiver.CandidateStructs) { var model = context.Compilation.GetSemanticModel(structDeclaration.SyntaxTree); if (model.GetDeclaredSymbol(structDeclaration) is not INamedTypeSymbol structSymbol) continue; if (!structSymbol.GetAttributes().Any(attr => attr.AttributeClass.Equals(bitLayoutAttr, SymbolEqualityComparer.Default))) continue; // 获取底层存储类型 var storageType = structSymbol.GetAttributes() .First(attr => attr.AttributeClass.Equals(bitLayoutAttr, SymbolEqualityComparer.Default)) .ConstructorArguments.FirstOrDefault().Value as ITypeSymbol ?? context.Compilation.GetTypeByMetadataName("System.Byte"); // 收集所有带BitOffset的位字段 var bitFields = structSymbol.GetMembers() .OfType<IFieldSymbol>() .Select(field => new { Field = field, Offset = field.GetAttributes() .First(attr => attr.AttributeClass.Equals(bitOffsetAttr, SymbolEqualityComparer.Default)) .ConstructorArguments.First().Value as int? ?? 0, BitType = field.Type, BitSize = field.Type.GetAttributes() .First(attr => attr.AttributeClass.Equals(bitSizeAttr, SymbolEqualityComparer.Default)) .ConstructorArguments.First().Value as int? ?? 0 }) .Where(bf => bf.BitSize > 0) .ToList(); if (!bitFields.Any()) continue; // 生成代码文件 var source = GenerateStructSource(structSymbol, storageType, bitFields); context.AddSource($"{structSymbol.Name}.g.cs", source); } } private string GenerateStructSource(INamedTypeSymbol structSymbol, ITypeSymbol storageType, List<dynamic> bitFields) { var namespaceName = structSymbol.ContainingNamespace.ToDisplayString(); var structName = structSymbol.Name; var storageTypeName = storageType.ToDisplayString(); var storageFieldName = $"_{storageType.Name.ToLowerInvariant()}Storage"; var sb = new StringBuilder(); sb.AppendLine($"namespace {namespaceName};"); sb.AppendLine($"public partial struct {structName}"); sb.AppendLine("{"); // 添加底层存储字段 sb.AppendLine($" [System.Runtime.InteropServices.FieldOffset(0)]"); sb.AppendLine($" private {storageTypeName} {storageFieldName};"); sb.AppendLine(); // 为每个位字段生成属性逻辑 foreach (var field in bitFields) { var fieldName = field.Field.Name; var bitTypeName = field.BitType.ToDisplayString(); var offset = field.Offset; var size = field.BitSize; var mask = (1 << size) - 1; sb.AppendLine($" public {bitTypeName} {fieldName}"); sb.AppendLine(" {"); sb.AppendLine($" get => ({bitTypeName})({storageFieldName} >> {offset} & {mask});"); sb.AppendLine($" set"); sb.AppendLine(" {"); sb.AppendLine($" {storageFieldName} &= ({storageTypeName})~({mask} << {offset});"); sb.AppendLine($" {storageFieldName} |= ({storageTypeName})(((int)value) << {offset});"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(); } sb.AppendLine("}"); return sb.ToString(); } } public class BitLayoutSyntaxReceiver : ISyntaxReceiver { public List<StructDeclarationSyntax> CandidateStructs { get; } = new List<StructDeclarationSyntax>(); public void OnVisitSyntaxNode(SyntaxNode node) { if (node is StructDeclarationSyntax structDec && structDec.AttributeLists.Any()) CandidateStructs.Add(structDec); } } }
3. 使用示例
定义部分结构体并标记特性,编译时生成器会自动补全位操作逻辑:
using BitLayout; // 指定底层用byte存储(8位) [BitLayout(StorageType = typeof(byte))] public partial struct BitTest { [BitOffset(0)] public Bit1 F1; [BitOffset(1)] public Bit2 F2; [BitOffset(3)] public Bit5 F3; }
使用时直接像普通字段一样访问:
var test = new BitTest(); test.F1 = true; test.F2 = 3; test.F3 = 16; Console.WriteLine(test.F1); // True Console.WriteLine(test.F2); // 3 Console.WriteLine(test.F3); // 16
关键注意事项
- 位序处理:默认实现用小端序(偏移0对应字节最低位),如需大端序可修改生成器的移位逻辑。
- 范围校验:位结构体内部已做取值范围校验,避免溢出。
- 存储类型适配:当总位数超过8位时,需指定
StorageType为ushort(16位)、uint(32位)或ulong(64位)。
内容的提问来源于stack exchange,提问作者sgf
相关产品推荐
相关产品推荐

