Jlzeng.BitSerializer 0.13.1

dotnet add package Jlzeng.BitSerializer --version 0.13.1
                    
NuGet\Install-Package Jlzeng.BitSerializer -Version 0.13.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Jlzeng.BitSerializer" Version="0.13.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Jlzeng.BitSerializer" Version="0.13.1" />
                    
Directory.Packages.props
<PackageReference Include="Jlzeng.BitSerializer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Jlzeng.BitSerializer --version 0.13.1
                    
#r "nuget: Jlzeng.BitSerializer, 0.13.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Jlzeng.BitSerializer@0.13.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Jlzeng.BitSerializer&version=0.13.1
                    
Install as a Cake Addin
#tool nuget:?package=Jlzeng.BitSerializer&version=0.13.1
                    
Install as a Cake Tool

BitSerializer

一个高性能的 .NET 位级别二进制序列化库。通过 Attribute 声明字段的位长度,Source Generator 在编译期自动生成序列化/反序列化代码,零反射开销,适用于网络协议解析、嵌入式通信、二进制文件格式处理等场景。

特性

  • 位级别精度 — 字段可以是任意位长度(如 4-bit、12-bit),不受字节边界限制
  • MSB / LSB 双模式 — 支持 MSB(高位优先)和 LSB(低位优先)两种位序编码
  • Attribute 驱动 — 通过特性标注即可声明序列化结构,无需手写编解码逻辑
  • 自动推断位长 — 未指定位长时,自动根据类型推断(byte = 8, ushort = 16, int = 32 ...)
  • 嵌套类型 — 支持嵌套复合类型的递归序列化
  • 继承链支持 — 支持多层继承,包括通过未标记 [BitSerialize] 的中间抽象类型(如泛型基类)正确序列化
  • 字符串支持 — 支持固定长度字符串([BitFixedString],可自定义 padding 字节)、NUL 终止字符串([BitTerminatedString])、长度前缀字符串([BitLengthPrefixString],length-prefix 8/16/32 bit)和字段引用长度字符串([BitLengthFieldString(nameof(NameLength))],长度由另一字段承载),支持 ASCII 和 UTF-8 编码
  • 自定义序列化类型 — 支持实现 IBitSerializable 接口的自定义类型,无需 [BitSerialize] 标记
  • 泛型类型参数 — 支持泛型类型参数字段的序列化,通过 IBitSerializable 接口在运行时分发
  • 集合支持 — 支持 List<T> / T[] 的序列化,元素数量可动态关联或固定指定;也支持按字节预算驱动嵌套动态元素集合(RelationKind = ByteLength)
  • 数组短包/剩余字节 — [BitFieldCount(N, PadIfShort=true)] 短数据自动补零;[BitFieldConsumeRemaining] 读取直到数据末尾
  • 声明式 CRC — [BitCrc] + [BitCrcInclude] 自动计算并回填 CRC 字段,内置 CRC-CCITT / CRC-16/ARC / CRC-32;[BitCrc(WholeBuffer = true)] 一键覆盖整 buffer 并用 SkipHeadBytes / SkipTailBytes 排除帧头/帧尾
  • 字段级大小端覆盖 — [BitField(Endian = BitEndian.Big/Little)] 让单个数值字段独立选择字节序,方便跨混合大小端的真实协议
  • 常量字段钉死与校验 — [BitFieldValue(0x7E)] 序列化时强制写入魔数、反序列化时自动校验,把帧头/帧尾/协议版本验证从 FluentValidation 前移到解码失败点
  • 嵌套类型按字节预算 — [BitFieldRelated(nameof(Length), RelationKind = ByteLength)] 既适用于 List 也适用于嵌套 [BitSerialize] 类型,序列化时自动按 nested 类型的实际字节数回填长度字段,反序列化时按 wire 长度严格校验
  • 自动回填关联字段 — 序列化时自动将集合长度写入关联的计数字段、将运行时类型写入多态判别字段,无需手动设置
  • 多态类型 — 通过类型判别字段自动分发到具体子类
  • 值转换器 — 支持自定义序列化/反序列化时的值变换,支持上下文感知重载
  • 序列化上下文与生命周期钩子 — 支持通过上下文对象传递状态,以及序列化/反序列化前后的回调
  • record 类型支持 — 支持 record class 和 record struct
  • Source Generator — 编译期自动生成序列化代码,零反射开销
  • 编译期诊断 — 自动检测嵌套类型是否缺少 [BitSerialize] 标记,编译时报错
  • 一包即用 — 只需引用 Jlzeng.BitSerializer,Source Generator 自动生效

环境要求

  • .NET 8.0 与 .NET 10.0(双 TFM / LTS) — 从 0.13.1 起同时面向 net8.0 与 net10.0 多目标构建;.NET 8 LTS 预计约 2026-11-10 结束支持,建议新项目优先选用 .NET 10。

快速开始

基本用法

using BitSerializer;

// 定义数据结构(需标记 [BitSerialize] 和 partial)
[BitSerialize]
public partial class Packet
{
    [BitField(8)]
    public byte Header { get; set; }

    [BitField(16)]
    public ushort Payload { get; set; }

    [BitField(8)]
    public byte Checksum { get; set; }
}

// 也支持 record 类型
[BitSerialize]
public partial record RecordPacket
{
    [BitField(8)]
    public byte Header { get; set; }

    [BitField(16)]
    public ushort Payload { get; set; }
}

// MSB 模式(高位优先,大端序)
var packet = new Packet { Header = 0xAB, Payload = 0x1234, Checksum = 0xCD };
byte[] bytes = BitSerializerMSB.Serialize(packet);
var result = BitSerializerMSB.Deserialize<Packet>(bytes);

// LSB 模式(低位优先,小端序)
byte[] lsbBytes = BitSerializerLSB.Serialize(packet);
var lsbResult = BitSerializerLSB.Deserialize<Packet>(lsbBytes);

高性能与零额外分配 API

BitSerializerMSB 和 BitSerializerLSB 提供相同的高性能入口:

int size = BitSerializerMSB.GetRequiredByteCount(packet);
Span<byte> buffer = stackalloc byte[size];

OperationStatus writeStatus =
    BitSerializerMSB.TrySerialize(packet, buffer, out int bytesWritten);

var reusable = new Packet();
OperationStatus readStatus =
    BitSerializerMSB.TryDeserializeInto(buffer, reusable, out int bytesConsumed);
  • TrySerialize 不扩容;目标过小时返回 DestinationTooSmall,成功时稳定热路径不产生库内临时分配。
  • TryDeserializeInto 复用顶层对象、嵌套对象、数组和 List。数值/值类型 List 须有足够 Capacity;引用类型 List 还须预先填充足够 Count,且每个元素对象非空;数组须有足够长度,引用元素也须预创建,否则返回 DestinationTooSmall。
  • TryDeserialize(ref T, ...) 也支持 struct 和 class;失败时对象可能已部分更新,需要事务语义时继续使用 Deserialize<T>()。
  • 输入不足返回 NeedMoreData,预分配对象图容量不足返回 DestinationTooSmall,CRC/判别值/预算格式错误返回 InvalidData;用户 Hook 与 Converter 的业务异常不会被吞掉。
  • 从 0.13.0 起,兼容 Deserialize<T>() 遇到未知多态判别值时抛 InvalidDataException(旧版本为 InvalidOperationException)。
  • 0.13.1:多目标 net8.0 + net10.0(双 TFM / LTS),NuGet 包同时包含两套运行时程序集。
  • 字符串反序列化仍必须创建最终 string;多态类型变化等语义要求创建新对象的情况不属于严格原地模式。
  • 内置 CRC 和字符串序列化使用无临时对象路径。自定义 CRC 可额外实现 IBitCrcAlgorithm<TSelf>;数值转换器可实现 IBitFieldValueConverter<TProperty, TWire> 或 IBitFieldValueConverter<TProperty, TWire, TContext> 避免装箱。
  • 首次 JIT、泛型初始化、用户 Hook/Context/Converter 内部行为和异常路径不计入稳定热路径的零分配承诺。

三泛型 Converter 的 TContext 来自模型的 SerializeContext() / DeserializeContext(),Generator 会直接生成强类型调用;TWire 决定 wire 读写类型,并在其位宽小于字段 BitLength 时报告 BITS063。

MSB vs LSB:两者的 API 完全一致,区别仅在于字节内的位序方向。MSB 适用于网络协议(大端序),LSB 适用于硬件寄存器、部分嵌入式协议(小端序)。

性能实测

环境:Debian GNU/Linux,x64;BenchmarkDotNet;warmup 4 / iteration 10;载荷约 200 bits(BenchmarkTests 项目同一用例)。对比对象含手写 Span 编解码与 BinarySerializer。

.NET 8.0.30 — Serialize

Method Mean Allocated
Manual_Ser 8.153 ns 0 B
BitSerializer_TrySer 54.037 ns 0 B
BitSerializer_Ser 54.499 ns 0 B
BinarySerializer_Ser 14168 ns 27928 B

.NET 8.0.30 — Deserialize

Method Mean Allocated
BitSerializer_TryDeInto 53.521 ns 0 B
Manual_De 97.880 ns 280 B
BitSerializer_De 110.717 ns 264 B
BinarySerializer_De 24003 ns 49016 B

.NET 10.0.11 — Serialize

Method Mean Allocated
Manual_Ser 5.881 ns 0 B
BitSerializer_TrySer 61.984 ns 0 B
BitSerializer_Ser 63.533 ns 0 B
BinarySerializer_Ser 11476 ns 27912 B

.NET 10.0.11 — Deserialize

Method Mean Allocated
BitSerializer_TryDeInto 30.379 ns 0 B
Manual_De 81.965 ns 280 B
BitSerializer_De 112.110 ns 264 B
BinarySerializer_De 18061 ns 46808 B

要点:

  • 相对 BinarySerializer:Serialize / Deserialize 仍是数量级差距,且 TrySerialize / TryDeInto 稳定热路径 0 B 分配。
  • 相对手写 baseline:Try* API 约在同一数量级(数十 ns),用 Attribute + Source Generator 换可维护性。
  • .NET 10 上:TryDeInto 约快 43%(53.5 → 30.4 ns);Manual / BinarySerializer 也更快;本机上 Ser / TrySer 略慢约 15%(54 → 62 ns 量级)——如实记录,不强行夸大。
  • 方法:BenchmarkTests 项目、相同 ~200 bits 载荷;不同机器/SDK 补丁结果会有波动。

0.13.1:多目标 net8.0;net10.0,CI 同步安装 8.0.x / 10.0.x SDK。

自动推断位长

不指定位长时,根据属性类型自动推断:

public class AutoData
{
    [BitField]          // 自动推断为 8 bit
    public byte A { get; set; }

    [BitField]          // 自动推断为 16 bit
    public ushort B { get; set; }

    [BitField]          // 自动推断为 32 bit
    public int C { get; set; }
}

自定义位长度(非字节对齐)

字段可以跨越字节边界,实现紧凑的位打包:

public class CompactData
{
    [BitField(4)]       // 高 4 位
    public byte NibbleHigh { get; set; }

    [BitField(4)]       // 低 4 位
    public byte NibbleLow { get; set; }

    [BitField(12)]      // 12 位跨字节
    public ushort TwelveBits { get; set; }

    [BitField(4)]
    public byte FourBits { get; set; }
}
// 总计 24 bits = 3 字节

枚举类型

枚举类型直接支持:

public enum Status : byte
{
    Unknown = 0,
    Active = 1,
    Inactive = 2,
}

public class StatusPacket
{
    [BitField(8)]
    public Status CurrentStatus { get; set; }

    [BitField(16)]
    public ushort Code { get; set; }
}

嵌套类型

支持复合类型的递归序列化。嵌套类型也需要标记 [BitSerialize] 和 partial:

[BitSerialize]
public partial class Point
{
    [BitField(8)]
    public byte X { get; set; }

    [BitField(8)]
    public byte Y { get; set; }
}

[BitSerialize]
public partial class Frame
{
    [BitField(8)]
    public byte Id { get; set; }

    [BitField]              // 自动推断嵌套类型的总位长
    public Point Position { get; set; } = new();

    [BitField(8)]
    public byte Flags { get; set; }
}

继承链

支持多层继承的序列化。中间基类即使未标记 [BitSerialize](如抽象泛型基类),其字段也会被正确包含:

[BitSerialize]
public partial record BasePacket
{
    [BitField(8)]
    public byte Header { get; set; }

    [BitField(16)]
    public ushort CommonField { get; set; }
}

// 中间抽象泛型基类,无需标记 [BitSerialize]
public abstract record GenericPacket<TPayload> : BasePacket
    where TPayload : PayloadBase
{
    [BitField]
    public TPayload Payload { get; set; }
}

// 具体子类标记 [BitSerialize],自动继承整个链上的字段
[BitSerialize]
public partial record ConcretePacket : GenericPacket<MyPayload> { }

生成的代码会自动调用 base.SerializeLSB() 处理基类字段,并序列化中间类型的字段。

泛型类型参数

泛型类型参数字段通过 IBitSerializable 接口在运行时动态分发,位长度在运行时计算:

public abstract record Container<T> : BaseType where T : PayloadBase
{
    [BitField]
    public T Data { get; set; }  // 运行时通过接口调用序列化
}

字符串

支持固定长度和 NUL 终止两种字符串序列化模式,默认 ASCII 编码,可选 UTF-8:

[BitSerialize]
public partial class StringPacket
{
    [BitField]
    [BitFixedString(10)]                // 固定 10 字节,不足补 NUL
    public string Name { get; set; } = "";

    [BitField]
    [BitTerminatedString]               // NUL 终止,动态长度
    public string Description { get; set; } = "";

    [BitField]
    [BitFixedString(16, Encoding = StringEncoding.UTF8)]  // UTF-8 编码
    public string Utf8Field { get; set; } = "";
}

自定义序列化类型(IBitSerializable)

实现 IBitSerializable 接口的类型可直接用作字段或集合元素,无需标记 [BitSerialize]:

public struct CustomHeader : IBitSerializable
{
    public byte Magic;
    public ushort Length;

    public int SerializeLSB(Span<byte> bytes, int bitOffset) { /* 手动写入 */ }
    public int SerializeMSB(Span<byte> bytes, int bitOffset) { /* 手动写入 */ }
    public int DeserializeLSB(ReadOnlySpan<byte> bytes, int bitOffset) { /* 手动读取 */ }
    public int DeserializeMSB(ReadOnlySpan<byte> bytes, int bitOffset) { /* 手动读取 */ }
    public int GetTotalBitLength() => 24;
}

[BitSerialize]
public partial class Packet
{
    [BitField(24)]
    public CustomHeader Header { get; set; }    // 自定义序列化类型

    [BitField(8)]
    [BitFieldCount(4)]
    public CustomHeader[] Items { get; set; }   // 也可作为集合元素
}

集合(List / Array)

通过 BitFieldRelated 关联计数字段,或通过 BitFieldCount 指定固定数量:

// 动态数量:关联计数字段
public class DynamicList
{
    [BitField(4)]
    public byte Count { get; set; }         // 序列化时自动从 Items.Count 回填

    [BitField(4)]
    public byte Reserved { get; set; }

    [BitField]
    [BitFieldRelated(nameof(Count))]        // 元素数量由 Count 字段决定
    public List<byte> Items { get; set; } = new();
}

// 序列化时无需手动设置 Count,会自动回填:
var data = new DynamicList { Reserved = 0, Items = [0x11, 0x22, 0x33] };
byte[] bytes = BitSerializerMSB.Serialize(data);
// data.Count 自动设为 3,溢出(超过 4-bit 最大值 15)时抛出异常

// 固定数量
public class FixedList
{
    [BitField]
    [BitFieldCount(3)]                      // 固定 3 个元素
    public List<byte> Items { get; set; } = new();
}
按字节预算驱动集合(RelationKind = ByteLength)

当线上协议的关联字段承载的不是元素个数,而是 集合占用的字节总数 时(典型例子:应用层帧 Length = Payload.Length + 4,或 Gal 这类 AppFrameLength 为应用帧区总字节数),使用 RelationKind = BitRelationKind.ByteLength:

// 线上 Length 字段 = Data.Length + 4;ValueConverterType 负责该常量换算
[BitSerialize]
public partial class AppFrameBase
{
    [BitField] public ushort Length { get; set; }
    [BitField] public ushort AppFrameType { get; set; }
    [BitField] public ushort Reserved { get; set; }

    [BitField]
    [BitFieldRelated(nameof(Length),
                     ValueConverterType = typeof(AppFrameLengthConverter),
                     RelationKind = BitRelationKind.ByteLength)]
    public byte[] Data { get; set; } = [];
}

// AppFrames 按总字节预算读取,直到预算耗尽
[BitSerialize]
public partial class Gal
{
    [BitField] public GalHead Head { get; set; } = new();
    [BitField] public ushort AppFrameLength { get; set; }

    [BitField]
    [BitFieldRelated(nameof(AppFrameLength),
                     RelationKind = BitRelationKind.ByteLength)]
    public List<AppFrameBase> AppFrames { get; set; } = [];
}

行为:

  • 序列化:先计算集合所有元素的实际字节总和 → 可选经过 ValueConverterType.OnSerializeConvert 换算为线上值 → 回填关联字段。
  • 反序列化:从关联字段读取线上值 → 可选经过 ValueConverterType.OnDeserializeConvert 还原为字节预算 → 在预算内循环解析元素,直到预算耗尽。
  • 元素可以是 byte[]、List<基础类型>,也可以是嵌套 [BitSerialize] 类型(含动态长度元素)。
  • ValueConverterType 在 ByteLength 模式下是 长度换算器(而不是列表值换算器),必须同时实现 OnSerializeConvert 与 OnDeserializeConvert(BITS024)。
  • 约束:不能与 [BitFieldCount] / [BitFieldConsumeRemaining] 共用(BITS025);静态元素位宽必须是 8 的倍数(BITS026);只能作用在集合上(BITS027)。
  • 运行时越界:元素越过预算 / 预算剩余未消费 / 预算超出剩余字节,都会抛出 InvalidDataException。

数组短包与消费剩余字节

针对定长缓冲但实际数据可能更短的场景(如 ATP BTM 报文:固定 276 字节但空报文为 0 字节):

[BitSerialize]
public partial class BtmPacket
{
    [BitField(8)]
    public byte Kind { get; set; }

    // 序列化:始终写入 276 字节;实际数据不足时末尾补 0
    // 反序列化:流不足 276 字节时读取实际可用部分,剩余填 default(T)
    [BitField]
    [BitFieldCount(276, PadIfShort = true)]
    public byte[] BtmBytes { get; set; } = [];
}

对于尾部变长数组,使用 [BitFieldConsumeRemaining](必须是末尾字段,元素必须是基本数值/枚举类型):

[BitSerialize]
public partial class Frame
{
    [BitField(8)] public byte Marker { get; set; }
    [BitField(16)] public ushort Kind { get; set; }

    // 序列化:按 array.Length 写入实际长度
    // 反序列化:从当前位置读到 bytes 末尾
    [BitField]
    [BitFieldConsumeRemaining]
    public byte[] Trailing { get; set; } = [];
}

声明式 CRC

[BitCrc] 标注 CRC 结果字段,[BitCrcInclude] 标注参与计算的字段。源码生成器在所有字段序列化完成后自动计算 CRC 并回填结果字段,无需 AfterSerialize 钩子或硬编码字节偏移:

using BitSerializer;
using BitSerializer.CrcAlgorithms;

[BitSerialize]
public partial class Frame
{
    [BitField(8)]
    public byte Start { get; set; } = 0x7E;

    [BitField(8), BitCrcInclude(nameof(Crc))]
    public byte DestAddr { get; set; }

    [BitField(8), BitCrcInclude(nameof(Crc))]
    public byte SrcAddr { get; set; }

    [BitField, BitFieldCount(8, PadIfShort = true), BitCrcInclude(nameof(Crc))]
    public byte[] Payload { get; set; } = [];

    // CRC-CCITT (poly 0x1021),序列化时自动计算并写入
    [BitField(16), BitCrc(typeof(CrcCcitt), InitialValue = 0)]
    public ushort Crc { get; set; }

    [BitField(8)]
    public byte End { get; set; } = 0xCF;
}
  • 内置算法(BitSerializer.CrcAlgorithms 命名空间):CrcCcitt(CRC-16-CCITT, poly 0x1021)、Crc16Arc(IBM CRC-16, poly 0x8005 反射)、Crc32(IEEE 802.3, poly 0xEDB88320 反射)
  • 可选参数:InitialValue(初始值)、ValidateOnDeserialize(反序列化时校验不匹配抛 InvalidDataException)
  • 自定义算法:实现 IBitCrcAlgorithm 接口(BitWidth / Reset(ulong) / Update(ReadOnlySpan<byte>) / Result),要求公共无参构造函数
  • 多层 CRC 支持:外层 [BitSerialize] 类型包含内层已带 CRC 的嵌套类型时,内层先计算,外层看到的是内层 CRC 已就位的字节

约束(编译期报错):

  • [BitCrcInclude] 字段必须连续且首尾字节对齐(BITS017)
  • [BitCrc] 结果字段必须字节对齐(BITS018)
  • 算法类型必须实现 IBitCrcAlgorithm 且有公共无参构造函数(BITS016)
  • [BitCrc] 字段不能同时用 [BitFieldRelated](避免 converter 产物被 CRC 覆盖,BITS021)
  • 动态长度 [BitSerialize] 基类的派生类型不能含 [BitCrc](运行时偏移会漂移,BITS022)
WholeBuffer 模式(覆盖整个 buffer + 头尾跳过)

逐字段 [BitCrcInclude] 在面向典型协议帧时往往写起来很啰嗦:所有 payload 字段都要标,多态/动态子类型在 BITS017 字节对齐限制下也不易表达。[BitCrc(WholeBuffer = true)] 提供了 "覆盖整个 buffer、再用 SkipHeadBytes / SkipTailBytes 跳掉帧头帧尾" 的对偶写法:

// DMI 风格帧:Sync(1) + Type(1) + Length(2) + Crc(2) + EOF(2),CRC 覆盖 Type + Length,
// 等价于 UartCrc16(bytes, offset = 1, length = totalBytes - 4)
[BitSerialize]
public partial class DmiFrame
{
    [BitField(8)] public byte Sync { get; set; }
    [BitField(8)] public byte Type { get; set; }
    [BitField(16)] public ushort Length { get; set; }
    [BitField(16), BitCrc(typeof(CrcCcitt),
        WholeBuffer = true,
        SkipHeadBytes = 1,          // 跳过 Sync
        SkipTailBytes = 4)]         // 跳过 Crc(2) + EOF(2)
    public ushort Crc { get; set; }
    [BitField(16)] public ushort EndOfFrame { get; set; }
}

// 动态 List + 多态子类型也能直接用,无需逐个 [BitCrcInclude]
[BitSerialize]
public partial class DynamicFrame
{
    [BitField(8)] public byte Count { get; set; }
    [BitField(8), BitFieldRelated(nameof(Count))]
    public List<byte> Data { get; set; } = new();
    [BitField(16), BitCrc(typeof(CrcCcitt), WholeBuffer = true, SkipTailBytes = 2)]
    public ushort Crc { get; set; }
}
  • SkipHeadBytes / SkipTailBytes 默认 0,必须 ≥ 0(BITS030)
  • 同一 CRC 字段不能同时使用 WholeBuffer = true 与逐字段 [BitCrcInclude](BITS029)
  • 编译期校验 CRC 自身字节 必须 落在 head/tail 跳过区内(BITS034),否则 CRC 会读到自己未初始化或过期的字节
  • 若同时存在前置 + 后置动态字段(CRC 自身会被运行时漂移挤出保护区),编译期拒绝(BITS035);要么把所有动态字段放在 CRC 前 + SkipTailBytes,要么放在后 + SkipHeadBytes,混排请改用 [BitCrcInclude] 模式

长度前缀字符串([BitLengthPrefixString])

很多协议字符串是 "1/2/4 字节长度前缀 + raw bytes" 的形式(典型例子:DMI 站名、TCP Modbus header)。这里提供一个声明式语法糖,免去手写 length 字段 + [BitFieldRelated] + 长度回填的样板代码:

[BitSerialize]
public partial class StationFrame
{
    [BitField(8)] public byte Header { get; set; }

    // 16-bit 长度前缀 + UTF-8 编码字符串,序列化时自动计算字节数并写入前缀
    [BitLengthPrefixString(16, Encoding = BitStringEncoding.UTF8)]
    public string StationName { get; set; } = "";

    // 可选 MaxBytes:限制编码后字节上限,UTF-8 自动做合法字符边界回退(不会切到半个字符)
    [BitLengthPrefixString(8, Encoding = BitStringEncoding.UTF8, MaxBytes = 32)]
    public string Tag { get; set; } = "";
}
  • LengthBits 必须是 8、16 或 32(BITS032),匹配常见的 byte / ushort / uint 长度字段
  • MaxBytes 默认 0 = 不限(仅受 LengthBits 容量约束),必须 ≥ 0(BITS036);序列化和反序列化两侧都强制
  • 编码后字节超过 LengthBits 上限或 MaxBytes 时抛 InvalidDataException(运行时)
  • UTF-8 短缩时会回退到最近的字符边界,避免序列化出半个字符
  • 字段起始必须字节对齐(BITS038),且不能位于 "运行时偏移可能漂移" 的动态字段之后(BITS039)

字段级大小端覆盖([BitField(Endian = ...)])

整个 BitSerializerMSB / BitSerializerLSB 决定了 "默认大端 / 默认小端",但真实协议常常同一帧里混用两种字节序——例如 DMI 帧用大端的 UART 包装但内部某些 ushort 字段是小端 Modbus 派生。[BitField(Endian = ...)] 让单个数值字段独立切换字节序,无需把整个类切到反向序列化器:

[BitSerialize]
public partial class HybridFrame
{
    [BitField(8)] public byte Sync { get; set; }

    // 帧体默认走外层序列化器的字节序
    [BitField(16)] public ushort Type { get; set; }

    // 这两个字段强制按小端写入,无视外层用的是 MSB 还是 LSB
    [BitField(16, Endian = BitEndian.Little)] public ushort LittlePayloadA { get; set; }
    [BitField(32, Endian = BitEndian.Little)] public uint LittlePayloadB { get; set; }

    // 这个字段强制按大端写入
    [BitField(16, Endian = BitEndian.Big)] public ushort BigChecksum { get; set; }
}
  • 三个枚举值:BitEndian.Inherit(默认,跟随外层)/ BitEndian.Big(MSB)/ BitEndian.Little(LSB)
  • 仅对 "字节对齐 + 字节倍数宽度的数值/枚举标量" 生效(BitStartIndex % 8 == 0 且 BitLength ∈ {8, 16, 32, 64},BITS028);不能用于位段/字符串/列表/嵌套
  • 不能跟在运行时偏移可能漂移的动态字段后(BITS033),否则字节序翻转可能退化为位排列腐蚀
  • 不能用越界 cast(如 (BitEndian)5)绕过枚举范围(BITS037)
  • 嵌套类型如果内部含 [BitField(Endian = ...)] 字段,外层把它放到非字节对齐位置 / 跟动态字段后会被编译期拒绝(BITS040 / BITS041),跨 assembly 也能正确检测

常量字段钉死([BitFieldValue(...)])

协议帧普遍有 FrameStart=0x7E / FrameEnd=0xCF / ProtocolVersion=1 这类魔数字段——序列化时要强制写入、反序列化时要校验。[BitFieldValue] 把这套逻辑前移到字段声明:

[BitSerialize]
public partial class Frame
{
    [BitField(8), BitFieldValue(0x7E)] public byte Start { get; set; }
    [BitField(16)] public ushort Payload { get; set; }
    [BitField(8), BitFieldValue(0xCF)] public byte End { get; set; }
}

// 序列化:无视用户给的值,强制写入常量,并把属性也回写为该常量
var src = new Frame { Start = 0x00, Payload = 0x1234, End = 0x00 };
byte[] bytes = BitSerializerMSB.Serialize(src);
// bytes[0] = 0x7E, bytes[3] = 0xCF;src.Start / src.End 也被改写为常量

// 反序列化:Verify=true(默认)时与常量不一致抛 InvalidDataException
var bad = new byte[] { 0xFF, 0x12, 0x34, 0xCF };
BitSerializerMSB.Deserialize<Frame>(bad);   // 抛 InvalidDataException

// 枚举字面量也接受
public enum FrameKind : byte { Heartbeat = 1, Data = 2 }
[BitField(8), BitFieldValue(FrameKind.Data)] public FrameKind Kind { get; set; }
  • 仅适用于数值/枚举标量(BITS042);常量必须能放进字段的 BitLength(BITS043)
  • 不能与 [BitCrc] / [BitFieldRelated] / [BitFieldCount] / [BitPoly] 共用(BITS044),这些机制会覆写 wire 字节
  • 仅接受整数字面量和枚举成员,不接受 float / string / Type / 数组(BITS045)
  • Verify = false 关闭反序列化校验(只是把读到的值赋回属性),适合"协议给什么就吃什么、再由调用方决定怎么处理"的场景

嵌套类型按字节预算(RelationKind = ByteLength on nested type)

以前 RelationKind = ByteLength 只能作用在 List/Array 上,嵌套类型不能用。v0.11.0 起放开了限制——嵌套 [BitSerialize] 类型也能像 list 一样按字节数自动回填、按字节预算严格校验:

[BitSerialize]
public partial class FrameContent
{
    [BitField(8)] public byte A { get; set; }
    [BitField(8)] public byte B { get; set; }
    [BitField(16)] public ushort C { get; set; }
}

[BitSerialize]
public partial class Frame
{
    [BitField(8)] public byte Sync { get; set; }
    // Length 必须先于 Content 声明(BITS052)
    [BitField(16)] public ushort Length { get; set; }
    // Content 是嵌套 [BitSerialize] 类型;Length 自动 = Content 序列化字节数
    [BitField, BitFieldRelated(nameof(Length), RelationKind = BitRelationKind.ByteLength)]
    public FrameContent Content { get; set; } = new();
    [BitField(8)] public byte End { get; set; }
}

// 序列化:Length 自动回填为 4(Content 占 4 字节)
var src = new Frame { Sync = 0x7E, Content = new() { A = 1, B = 2, C = 3 }, End = 0xCF };
byte[] bytes = BitSerializerMSB.Serialize(src);  // Length 自动写入

// 反序列化:从 Length 读字节数 → 反序列化 Content → 验证 Content 消耗的 bit 数 == Length × 8
// 如果不一致(如帧错乱)抛 InvalidDataException
  • 长度字段必须 先于 嵌套字段声明(BITS052),反序列化才能先知道字节预算
  • 嵌套类型的静态位长必须是 8 的倍数(BITS051);动态长度嵌套类型在运行时校验
  • 支持可选 ValueConverterType 在线上长度与字节数之间换算(与 list ByteLength 一致),converter 必须同时实现序列化/反序列化方向(BITS024)
  • 同时适用于:静态嵌套 [BitSerialize]、动态嵌套 [BitSerialize]、IBitSerializable 类型、泛型类型参数

字段引用长度字符串([BitLengthFieldString])

区别于 [BitLengthPrefixString](长度内联在字符串前)——这里字符串的字节数来自 另一个独立声明的字段。典型 DMI 协议:站名 / 提示文本的长度字段位置和字符串位置由协议固定,无法自描述。

[BitSerialize]
public partial class StationFrame
{
    [BitField(8)] public byte Header { get; set; }
    [BitField(8)] public byte NameLength { get; set; }      // 长度字段
    [BitLengthFieldString(nameof(NameLength), Encoding = BitStringEncoding.UTF8)]
    public string Name { get; set; } = "";
    [BitField(8)] public byte Footer { get; set; }
}

// 序列化:NameLength 自动 = UTF8 字节数;序列化后 src.NameLength 同步更新
var src = new StationFrame { Header = 0xAB, Name = "Tokyo", Footer = 0xCD };
byte[] bytes = BitSerializerMSB.Serialize(src);
// bytes = [0xAB, 5, 'T','o','k','y','o', 0xCD],src.NameLength 已自动设为 5
  • 引用字段必须是 byte/sbyte/short/ushort/int/uint,BitLength ∈ {8, 16, 32}(BITS047)
  • 引用字段必须 先于 字符串字段声明(BITS047)
  • 字符串字段必须从字节边界开始(BITS048)且不能跟在运行时偏移可能漂移的字段后(BITS049)
  • 可选 MaxBytes 限制编码字节上限(≥ 0,BITS050),UTF-8 截断自动避开多字节字符中间

多绑定 [BitFieldRelated](v0.12.0+)

同一字段可以同时贴 2 个 [BitFieldRelated] —— 一个 Count(判别值)+ 一个 ByteLength(字节预算)。 典型用法:多态字段绑定 Type 字段(决定类型)+ Length 字段(决定字节数):

[BitSerialize]
public partial class FrameHeader
{
    [BitField(8)]  public byte Type { get; set; }
    [BitField(16)] public ushort Length { get; set; }

    [BitField]
    [BitFieldRelated(nameof(Type))]                                          // 判别字段
    [BitFieldRelated(nameof(Length), RelationKind = BitRelationKind.ByteLength)] // 字节预算
    [BitPoly(1, typeof(SubFrameA))]
    [BitPoly(2, typeof(SubFrameB))]
    public FrameContent Content { get; set; }
}

序列化时:

  1. Type 自动按运行时 Concrete 类型写入对应的 TypeId
  2. Length 自动按 Content 的 GetTotalBitLength()/8 回填
  3. Content payload 写入

反序列化时:先读 Type 选具体类型、读 Length 拿到字节预算,dispatch 解码后断言 consumedBits == declaredBytes * 8,wire 长度与实际 payload 不一致直接 InvalidDataException。

约束(编译期校验):

  • 最多 2 个 [BitFieldRelated](BITS056)
  • 两个的 RelationKind 必须不同(BITS058)
  • 多绑定模式仅 polymorphic 字段支持(BITS057)—— list/nested 单个 binding 已能表达
  • 声明顺序无关,分析器按 RelationKind 把 Count 当 primary

多态类型

通过 BitPoly 特性实现基于判别值的类型分发:

public class BaseMessage
{
    [BitField(8)]
    public byte CommonField { get; set; }
}

public class MessageA : BaseMessage
{
    [BitField(8)]
    public byte FieldA { get; set; }
}

public class MessageB : BaseMessage
{
    [BitField(16)]
    public ushort FieldB { get; set; }
}

public class Container
{
    [BitField(8)]
    public byte MessageType { get; set; }           // 序列化时自动从 Message 的运行时类型回填

    [BitField(24)]                                  // 需指定所有子类的最大位长
    [BitFieldRelated(nameof(MessageType))]          // 关联判别字段
    [BitPoly(1, typeof(MessageA))]                  // MessageType=1 → MessageA
    [BitPoly(2, typeof(MessageB))]                  // MessageType=2 → MessageB
    public BaseMessage Message { get; set; }
}

// 序列化时无需手动设置 MessageType,会自动根据运行时类型回填:
var data = new Container { Message = new MessageA { CommonField = 0xAA, FieldA = 0xBB } };
byte[] bytes = BitSerializerMSB.Serialize(data);
// data.MessageType 自动设为 1

值转换器

实现 IBitFieldValueConverter 接口,自定义序列化/反序列化时的值变换。值转换器可用于所有字段类型(数值、枚举、字符串、嵌套类型、多态、泛型参数、集合)。

接口的四个方法均为 static virtual,只需实现你关心的重载即可:

public class DoubleConverter : IBitFieldValueConverter
{
    // 只实现反序列化转换——序列化侧不会调用转换器
    public static object OnDeserializeConvert(object value)
    {
        return (byte)((byte)value * 2);     // 反序列化时乘 2
    }
}

public class ConvertedData
{
    [BitField(8)]
    [BitFieldRelated(nameof(Value), typeof(DoubleConverter))]
    public byte Value { get; set; }
}

上下文感知值转换器

值转换器可以接收上下文对象,用于在转换时访问额外状态。只需重写带 context 参数的重载:

public class OffsetConverter : IBitFieldValueConverter
{
    // 基础重载(必须实现)
    public static object OnDeserializeConvert(object value) => value;
    public static object OnSerializeConvert(object value) => value;

    // 上下文感知重载(可选)
    public static object OnDeserializeConvert(object value, object? context)
    {
        if (context is int offset)
            return (byte)((byte)value + offset);
        return value;
    }

    public static object OnSerializeConvert(object value, object? context)
    {
        if (context is int offset)
            return (byte)((byte)value - offset);
        return value;
    }
}

Source Generator 会独立检测每一侧的上下文重载:只重写了 OnSerializeConvert(object, object?) 的转换器,反序列化侧仍会调用无上下文的 OnDeserializeConvert(object),反之亦然。

序列化上下文与生命周期钩子

类型可以提供上下文对象并在序列化/反序列化前后执行回调,实现校验和计算、数据填充等逻辑:

[BitSerialize]
public partial class ChecksumPacket
{
    [BitField(8)]
    public byte Header { get; set; }

    [BitField(16)]
    public ushort Data { get; set; }

    [BitField(8)]
    public byte Checksum { get; set; }

    // 提供序列化上下文(传递给嵌套类型和值转换器)
    public object? SerializeContext() => this;
    public object? DeserializeContext() => this;

    // 序列化后回调:计算校验和
    public void AfterSerialize(object? context, Span<byte> bytes)
    {
        Checksum = CalculateChecksum(bytes);
    }

    // 反序列化后回调:验证校验和
    public void AfterDeserialize(object? context, ReadOnlySpan<byte> bytes)
    {
        if (Checksum != CalculateChecksum(bytes))
            throw new InvalidDataException("Checksum mismatch");
    }
}

生命周期钩子的完整调用顺序:

钩子 序列化时 反序列化时
SerializeContext() / DeserializeContext() 获取上下文对象 获取上下文对象
BeforeSerialize() / BeforeDeserialize() 序列化字段前 反序列化字段前
AfterSerialize() / AfterDeserialize() 所有字段序列化后 所有字段反序列化后

用户也可以通过 partial void OnSerializing(object? context) / partial void OnDeserializing(object? context) 拦截生成代码的入口。

注意:上下文和钩子仅用于辅助序列化逻辑(如值转换、校验和计算),不应改变类型的总位长度。

忽略字段

使用 BitIgnore 跳过不需要序列化的字段:

public class MixedData
{
    [BitField(8)]
    public byte Value { get; set; }

    [BitIgnore]                         // 不参与序列化
    public string Description { get; set; } = "";

    [BitField(8)]
    public byte AnotherValue { get; set; }
}

编译期诊断

Source Generator 会在编译期检查常见错误并报告诊断信息:

诊断码 说明
BITS001 成员缺少 [BitField] 或 [BitIgnore]
BITS002 不支持的字段类型
BITS003 List 缺少计数信息(需 [BitFieldRelated] 或 [BitFieldCount])
BITS004 值转换器未实现 IBitFieldValueConverter
BITS005 标记 [BitSerialize] 的类型必须是 partial
BITS006 嵌套的序列化字段类型必须标记 [BitSerialize]
BITS007 多态成员缺少判别字段关联
BITS008 关联成员未找到
BITS009 成员有序列化辅助特性但缺少 [BitField]
BITS010 [BitFixedString] 只能用于 string 类型
BITS011 [BitTerminatedString] 只能用于 string 类型
BITS012 [BitFixedString] 字节长度必须为正数
BITS013 泛型类型参数缺少 new() 或 struct 约束
BITS014 引用类型的 [BitField] 成员缺少默认值(序列化前可能为 null)
BITS015 [BitCrcInclude] 目标字段不存在或不是 [BitCrc]
BITS016 [BitCrc] 算法未实现 IBitCrcAlgorithm 或缺公共无参构造函数
BITS017 [BitCrcInclude] 字段必须连续且首尾字节对齐
BITS018 [BitCrc] 结果字段必须字节对齐
BITS019 [BitFieldConsumeRemaining] 必须是末尾基本元素字段
BITS020 [BitFieldCount(PadIfShort=true)] 仅支持基本数值/枚举元素
BITS021 [BitCrc] 不能与 [BitFieldRelated] / converter 共用
BITS022 动态长度 [BitSerialize] 基类不能含 [BitCrc]
BITS023 [BitField(N)] 把固定位长贴在运行时大小可变的字段上(多态/manual IBitSerializable,警告)
BITS024 RelationKind=ByteLength 下的 converter 必须同时实现序列化/反序列化方向
BITS025 RelationKind=ByteLength 不能与 [BitFieldCount] / [BitFieldConsumeRemaining] 共用
BITS026 RelationKind=ByteLength 的静态元素位宽必须是 8 的倍数
BITS027 RelationKind=ByteLength 只能作用在 List/Array 上
BITS028 [BitField(Endian = ...)] 仅对字节对齐 + 字节倍数宽度的数值/枚举标量生效
BITS029 [BitCrc(WholeBuffer = true)] 不能与 [BitCrcInclude] 共用
BITS030 [BitCrc] 的 SkipHeadBytes / SkipTailBytes 必须 ≥ 0
BITS031 [BitLengthPrefixString] 仅能用于 string 类型
BITS032 [BitLengthPrefixString] 的 LengthBits 必须是 8、16 或 32
BITS033 [BitField(Endian = ...)] 不能跟在运行时偏移可能漂移的动态字段之后
BITS034 [BitCrc(WholeBuffer = true)] 的 SkipHead/SkipTail 必须把 CRC 自身字节排除在保护区外
BITS035 [BitCrc(WholeBuffer = true)] 的 CRC 槽位被前置 + 后置混排动态字段挤出保护区时编译期拒绝
BITS036 [BitLengthPrefixString] 的 MaxBytes 必须 ≥ 0(0 表示不限)
BITS037 [BitField(Endian = ...)] 不能用越界 cast(如 (BitEndian)5)
BITS038 [BitLengthPrefixString] 字段必须从字节边界开始
BITS039 [BitLengthPrefixString] 不能跟在运行时偏移可能漂移的动态字段之后
BITS040 嵌入的类型含 [BitField(Endian = ...)] 时父字段必须落在字节对齐位置
BITS041 嵌入的类型含 [BitField(Endian = ...)] 时父字段不能跟在运行时漂移的动态字段后
BITS042 [BitFieldValue] 仅适用于数值/枚举标量
BITS043 [BitFieldValue] 常量超出字段 BitLength 可表示范围
BITS044 [BitFieldValue] 不能与 [BitCrc] / [BitFieldRelated] / [BitFieldCount] / [BitPoly] 共用
BITS045 [BitFieldValue] 仅接受整数字面量或枚举成员(不接受 float / string / Type / 数组)
BITS046 [BitLengthFieldString] 仅能用于 string 类型
BITS047 [BitLengthFieldString] 引用的长度字段未找到,或不是先于本字段声明的 byte/ushort/uint 标量
BITS048 [BitLengthFieldString] 字段必须从字节边界开始
BITS049 [BitLengthFieldString] 不能跟在运行时偏移可能漂移的动态字段之后
BITS050 [BitLengthFieldString] 的 MaxBytes 必须 ≥ 0(0 = 不限)
BITS051 [BitFieldRelated(ByteLength)] 嵌套类型的静态位长必须是 8 的倍数
BITS052 [BitFieldRelated(ByteLength)] 嵌套类型的长度字段必须先于嵌套字段声明
BITS063 强类型 Converter 的 TWire 位宽小于字段 BitLength

例如,以下代码会触发 BITS006 编译错误:

// 错误:Point 未标记 [BitSerialize]
public record Point
{
    [BitField(8)] public byte X { get; set; }
}

[BitSerialize]
public partial record Frame
{
    [BitField] public Point Position { get; set; }  // BITS006: 'Point' 必须标记 [BitSerialize]
}

修复方式:为 Point 添加 [BitSerialize] 和 partial。

Attribute 参考

特性 说明
[BitSerialize] 标记类型参与位序列化(类型必须同时声明为 partial)
[BitField(n)] 声明字段参与序列化,n 为位长度(可选,不指定则自动推断)
[BitField(n, Endian = BitEndian.Big/Little)] 字段级大小端覆盖(仅字节对齐 + 字节倍数宽度的标量),Inherit(默认)跟随外层序列化器
[BitFieldValue(const, Verify = true)] 钉死常量值:序列化时强制写入、反序列化时校验不一致抛 InvalidDataException
[BitFieldRelated(name)] 关联另一个字段(用于 List 计数或多态判别)
[BitFieldRelated(name, converterType)] 关联字段并指定值转换器(默认 Count 模式下为列表值转换器)
[BitFieldRelated(name, RelationKind = ByteLength)] 关联字段承载集合的字节预算(见按字节预算驱动集合)
[BitFieldCount(n)] 指定 List / Array 的固定元素数量
[BitFieldCount(n, PadIfShort=true)] 定长集合,短数据自动补默认值(仅基本元素)
[BitFieldConsumeRemaining] 尾部变长集合,读到数据末尾(仅基本元素,必须是末尾字段)
[BitCrc(typeof(Algo), InitialValue, ValidateOnDeserialize)] CRC 结果字段,指定算法
[BitCrc(typeof(Algo), WholeBuffer = true, SkipHeadBytes, SkipTailBytes)] 整 buffer CRC 模式,用 SkipHead/SkipTail 排除帧头/帧尾
[BitCrcInclude(nameof(CrcField))] 标注参与 CRC 计算的字段(与 WholeBuffer = true 互斥)
[BitPoly(id, type)] 多态映射:当判别值为 id 时反序列化为 type
[BitFixedString(n)] 固定长度字符串(n 字节),不足补 NUL,可选 Encoding 参数
[BitFixedString(n, Padding = 0x20)] 自定义填充字节(如 ASCII 空格 0x20),反序列化时同步 trim
[BitTerminatedString] NUL 终止字符串,动态长度,可选 Encoding 参数
[BitLengthPrefixString(lengthBits)] 长度前缀字符串(lengthBits ∈ {8, 16, 32}),可选 Encoding / MaxBytes 参数
[BitLengthFieldString(nameof(LengthField))] 长度由另一字段承载的字符串,可选 Encoding / MaxBytes;序列化时自动反向回填长度字段
[BitIgnore] 忽略该字段,不参与序列化/反序列化

支持的数据类型

  • 整数类型:byte, sbyte, short, ushort, int, uint, long, ulong
  • 枚举类型(任意底层整数类型)
  • 字符串:固定长度([BitFixedString])、NUL 终止([BitTerminatedString])、长度前缀([BitLengthPrefixString])、字段引用长度([BitLengthFieldString])
  • 嵌套的 BitField 类型(class / struct / record)
  • 自定义 IBitSerializable 类型
  • List<T> / T[](T 为上述支持的类型)

API 参考

类 说明
BitSerializerMSB MSB 模式序列化器(高位优先,大端字节序)
BitSerializerLSB LSB 模式序列化器(低位优先,小端字节序)

两者提供完全相同的 API:

// 泛型序列化
byte[] bytes = BitSerializerMSB.Serialize(obj);       // 返回 byte[]
BitSerializerMSB.Serialize(obj, spanBuffer);           // 写入 Span<byte>

// 泛型反序列化
T result = BitSerializerMSB.Deserialize<T>(bytes);     // 从 byte[]
T result = BitSerializerMSB.Deserialize<T>(span);      // 从 ReadOnlySpan<byte>

// 非泛型序列化(通过 Type 参数指定类型)
byte[] bytes = BitSerializerMSB.Serialize(obj, typeof(Packet));       // 返回 byte[]
BitSerializerMSB.Serialize(obj, typeof(Packet), spanBuffer);          // 写入 Span<byte>

// 非泛型反序列化(通过 Type 参数指定类型,返回 object)
object result = BitSerializerMSB.Deserialize(bytes, typeof(Packet));  // 从 byte[]
object result = BitSerializerMSB.Deserialize(span, typeof(Packet));   // 从 ReadOnlySpan<byte>

将 MSB 替换为 LSB 即可切换为低位优先模式。

非泛型 API 适用于编译期无法确定类型的场景(如通过配置或反射动态确定类型),返回 object,需自行转换为目标类型。

自动发布到 NuGet

仓库内已提供 GitHub Actions 工作流 .github/workflows/publish-nuget.yml,会在推送 v* 标签时自动构建、测试、打包并发布到 nuget.org。

发布前需要完成一次性配置:

  1. 在 nuget.org 创建 API Key,并授予推送对应包的权限。
  2. 在 GitHub 仓库中打开 Settings → Secrets and variables → Actions。
  3. 新增仓库机密 NUGET_API_KEY,值填写上一步的 API Key。

之后每次发布:

  1. 更新项目版本号,或直接使用 tag 作为发布版本。
  2. 推送形如 v0.7.3 的 Git tag。
  3. GitHub Actions 会自动把包发布到 https://api.nuget.org/v3/index.json。

许可证

MIT License

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.13.1 98 9/23/2026
0.13.0 133 7/24/2026
0.12.2 131 5/25/2026
0.12.1 112 5/25/2026
0.12.0 109 5/25/2026
0.11.0 126 5/24/2026
0.10.0 112 5/24/2026
0.9.0 118 4/17/2026
0.8.2 116 4/17/2026
0.8.1 120 4/17/2026
0.8.0 128 4/16/2026
0.7.3 116 4/13/2026
0.7.2 116 4/10/2026
0.7.1 111 4/10/2026
0.7.0 124 4/10/2026
0.6.8 119 4/5/2026
0.6.6 110 4/5/2026
0.6.5 116 4/5/2026
0.6.4 119 4/4/2026
0.6.3 110 4/4/2026
Loading failed