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

如何在C#中实现无第三方库的自定义数据序列化与反序列化?

在C#中不依赖第三方库实现自定义二进制序列化/反序列化(处理复杂层级结构与对象关系)

我正在做一个项目,需要在C#里不依赖任何第三方库实现自定义的序列化和反序列化逻辑,核心要处理复杂的数据结构并保留对象关系。目前我有一组带层级结构的自定义类,需要把这些类的实例转换成自定义二进制格式,用来高效存储和传输。

以下是简化后的类层级示例:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public List<Address> Addresses { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

想请教下针对这类场景实现序列化/反序列化的最佳实践、适用的设计模式以及可以利用的C#特性。


一、核心最佳实践

  • 固定二进制格式规范:先明确每个类型的二进制布局,比如基础类型(int、string)的存储规则,集合的前缀标识(先存集合长度,再逐个存元素)。例如string可先存int类型的字节长度,再存UTF-8字节数组;List<T>先存元素个数,再依次序列化每个T实例。
  • 处理对象引用与循环引用:如果存在重复引用或循环引用(比如Person实例引用另一个Person),给每个对象分配唯一ID,序列化时先存ID;反序列化时维护对象缓存字典,用ID映射已创建的对象,避免重复创建或死循环。
  • 版本兼容性:在二进制流开头加入版本号字段,后续格式变更时,反序列化可根据版本号适配不同解析逻辑,避免旧数据无法读取。
  • 验证与错误处理:序列化/反序列化过程中加入校验(比如校验集合长度是否合法、字符串字节数是否匹配),抛出明确异常信息,方便调试。
  • 性能优化:使用BinaryWriter/BinaryReader替代手动字节操作,降低出错概率;对频繁序列化的类型,提前缓存反射信息(如PropertyInfo、FieldInfo),减少反射开销;性能要求极高时,可生成IL代码动态实现序列化逻辑。

二、适用的设计模式

  • 策略模式:为不同类型(如Person、Address、List<T>)定义各自的序列化/反序列化策略类,核心序列化器根据类型选择对应策略执行,便于扩展新类型。
  • 工厂模式:创建序列化器工厂,根据类型或标识生成对应的处理实例,解耦类型与处理逻辑。
  • 备忘录模式:序列化本质是将对象状态保存为二进制“备忘录”,反序列化是恢复状态,该模式天然适配序列化场景。

三、可利用的C#特性

  • BinaryWriter/BinaryReader:.NET基础库自带的二进制流读写类,可直接处理基础类型(int、bool、byte[]等),比手动操作MemoryStream更高效安全。
  • 反射(Reflection):通过反射自动遍历类的属性/字段,无需为每个类写硬编码序列化逻辑,适配复杂层级结构。可用Type.GetProperties()或Type.GetFields()获取成员,结合自定义特性标记需要序列化的成员。
  • 自定义特性(Custom Attributes):定义[SerializableMember]特性标记需序列化的属性/字段,过滤无关成员;还可通过特性指定成员的二进制顺序、版本兼容信息等。
  • 泛型(Generics):针对List<T>、Dictionary<TKey, TValue>等集合类型编写泛型序列化方法,无需为每个集合类型单独实现逻辑。
  • IDisposable:BinaryWriter/BinaryReader和MemoryStream均实现IDisposable,用using语句确保资源正确释放,避免内存泄漏。

示例实现片段

以下是针对Person和Address的极简序列化/反序列化示例:

序列化逻辑

public static byte[] SerializePerson(Person person)
{
    using var ms = new MemoryStream();
    using var writer = new BinaryWriter(ms);

    // 序列化Name:先存长度,再存字节
    var nameBytes = Encoding.UTF8.GetBytes(person.Name);
    writer.Write(nameBytes.Length);
    writer.Write(nameBytes);

    // 序列化Age
    writer.Write(person.Age);

    // 序列化Addresses:先存数量,再逐个序列化
    writer.Write(person.Addresses.Count);
    foreach (var addr in person.Addresses)
    {
        var streetBytes = Encoding.UTF8.GetBytes(addr.Street);
        writer.Write(streetBytes.Length);
        writer.Write(streetBytes);

        var cityBytes = Encoding.UTF8.GetBytes(addr.City);
        writer.Write(cityBytes.Length);
        writer.Write(cityBytes);
    }

    return ms.ToArray();
}

反序列化逻辑

public static Person DeserializePerson(byte[] data)
{
    using var ms = new MemoryStream(data);
    using var reader = new BinaryReader(ms);

    var person = new Person();

    // 反序列化Name
    var nameLength = reader.ReadInt32();
    var nameBytes = reader.ReadBytes(nameLength);
    person.Name = Encoding.UTF8.GetString(nameBytes);

    // 反序列化Age
    person.Age = reader.ReadInt32();

    // 反序列化Addresses
    var addrCount = reader.ReadInt32();
    person.Addresses = new List<Address>(addrCount);
    for (int i = 0; i < addrCount; i++)
    {
        var addr = new Address();
        var streetLength = reader.ReadInt32();
        var streetBytes = reader.ReadBytes(streetLength);
        addr.Street = Encoding.UTF8.GetString(streetBytes);

        var cityLength = reader.ReadInt32();
        var cityBytes = reader.ReadBytes(cityLength);
        addr.City = Encoding.UTF8.GetString(cityBytes);

        person.Addresses.Add(addr);
    }

    return person;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 23:30:23