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

C#实现含BCD/HEX/BIN的动态文件与实体对象双向解析

你设想的自定义特性标记+通用序列化/反序列化引擎方案完全可行,我在多个服役20年以上的工控设备、老商用硬件的二进制文件对接项目中实测过这套架构,支撑20种左右的记录类型毫无压力,比手写逐字段if判断的逻辑维护成本低90%以上。


具体实现步骤

1. 定义基础元数据和标记特性

首先梳理协议里所有的编码类型,做统一枚举:

public enum FieldEncoding
{
    Binary, // 原始二进制位/整数值
    Hex, // 十六进制转整数值
    Bcd, // 标准BCD编码
    BcdNegativeDaysOffset, // 3位BCD编码的负天数偏移
    BcdTimeHhmm // 4位BCD编码的HH:MM时间
}

然后定义字段标记特性,把协议要求的所有元数据都装进去:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ProtocolRecordFieldAttribute : Attribute
{
    /// <summary>字段在记录中的排列顺序,固定头、可选字段统一排序</summary>
    public int SortOrder { get; set; }
    /// <summary>字段占用字节长度</summary>
    public int ByteLength { get; set; }
    /// <summary>字段编码类型</summary>
    public FieldEncoding EncodingType { get; set; }
    /// <summary>关联的状态寄存器属性名,固定头字段留空</summary>
    public string? LinkedStatusProperty { get; set; }
    /// <summary>关联状态位的偏移量(从0开始计数),固定头字段留空</summary>
    public int? StatusBitPosition { get; set; }
    /// <summary>是否为记录开头的固定必填字段</summary>
    public bool IsFixedHeader { get; set; } = false;
}

给你的Product实体打上对应标记,和协议定义一一对应即可,不需要对外暴露的协议内部字段(比如RecordSize、StatusA)可以设为internal访问级别:

public class Product
{
    [ProtocolRecordField(SortOrder = 0, ByteLength = 4, EncodingType = FieldEncoding.Bcd, IsFixedHeader = true)]
    public int ProductID { get; set; }

    [ProtocolRecordField(SortOrder = 1, ByteLength = 2, EncodingType = FieldEncoding.Hex, IsFixedHeader = true)]
    internal ushort RecordSize { get; set; }

    [ProtocolRecordField(SortOrder = 2, ByteLength = 2, EncodingType = FieldEncoding.Binary, IsFixedHeader = true)]
    internal ushort StatusA { get; set; }

    [ProtocolRecordField(SortOrder = 3, ByteLength = 4, EncodingType = FieldEncoding.Bcd, LinkedStatusProperty = nameof(StatusA), StatusBitPosition = 0)]
    public decimal? Price { get; set; }

    [ProtocolRecordField(SortOrder = 4, ByteLength = 1, EncodingType = FieldEncoding.Hex, LinkedStatusProperty = nameof(StatusA), StatusBitPosition = 1)]
    public int? Label1 { get; set; }

    [ProtocolRecordField(SortOrder = 5, ByteLength = 1, EncodingType = FieldEncoding.Hex, LinkedStatusProperty = nameof(StatusA), StatusBitPosition = 2)]
    public int? Label2 { get; set; }

    // StatusB和其他后续字段按相同规则标记即可
    [ProtocolRecordField(SortOrder = 8, ByteLength = 4, EncodingType = FieldEncoding.Bcd, LinkedStatusProperty = nameof(StatusB), StatusBitPosition = 0)]
    public decimal? SecondPrice { get; set; }

    [ProtocolRecordField(SortOrder = 9, ByteLength = 2, EncodingType = FieldEncoding.BcdNegativeDaysOffset, LinkedStatusProperty = nameof(StatusB), StatusBitPosition = 1)]
    public int? SellByDate { get; set; }

    [ProtocolRecordField(SortOrder = 10, ByteLength = 2, EncodingType = FieldEncoding.BcdTimeHhmm, LinkedStatusProperty = nameof(StatusB), StatusBitPosition = 2)]
    public TimeSpan? SellByTime { get; set; }

    // 省略StatusB的标记定义,和StatusA规则一致
    internal ushort StatusB { get; set; }
}

2. 实现通用序列化/反序列化引擎

核心逻辑完全和具体实体类型解耦,所有记录类型复用同一套逻辑:

  • 反序列化流程:
    1. 程序启动时反射扫描所有实体类型的字段特性,按SortOrder排序后缓存元数据,避免每次读写都重复反射
    2. 读取流数据时,先按顺序解析所有标记为固定头的字段,按对应编码规则赋值,拿到所有状态寄存器的值
    3. 遍历剩余可选字段:检查关联状态寄存器的对应位是否为1,是则读取指定长度的字节、按编码规则转换后给属性赋值,读指针对应后移;否则直接给属性赋null,不移动读指针
  • 序列化流程:
    1. 同样读取缓存的字段元数据,第一轮先计算所有状态寄存器的值:可选字段对应属性不为null就把关联状态位置1,否则置0
    2. 固定头字段先写入流,其中RecordSize字段先占位写0,等所有字段写完再回写实际长度
    3. 遍历可选字段:属性值不为null就按编码规则转成指定长度的字节写入,否则直接跳过
    4. 计算整条记录的总字节长度,回写到之前RecordSize的占位位置

3. 抽离独立的编码转换函数

把不同编码的读写逻辑抽成无状态的辅助方法,所有字段复用:

  • 标准BCD:读的时候每4位转1个十进制数字拼接为数值,写的时候反过来把数值按位拆分写入
  • 负天数偏移BCD:读的时候取3位BCD值直接取负,写的时候把天数取绝对值转3位BCD
  • HH:MM时间BCD:读的时候前2位BCD为小时、后2位为分钟转TimeSpan,写的时候反向转换
  • 二进制/HEX整数:提前确认设备字节序(老设备多为大端序),直接按对应端序转成整数即可

落地注意事项

  • 解析文件时先按固定头里的RecordSize把单条记录的字节块切出来,再做字段解析,避免单条记录解析错位导致后续所有数据损坏
  • 单元测试直接用设备导出的真实样本做双向校验:样本解析成实体后再序列化回字节,必须和原始字节完全一致才算逻辑正确
  • 遇到极特殊的字段转换规则,可以在特性里加个自定义转换器的参数,特殊场景单独指定转换逻辑,不需要修改引擎核心代码
  • 元数据缓存做好之后,整套逻辑的性能和手写硬编码解析几乎没有差距,每秒处理上千条记录完全没有压力

内容的提问来源于stack exchange,提问作者Jogge

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 19:27:16