You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现类似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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:17:10