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

C#中如何将复杂数据结构保存并加载到二进制文件?

问题描述

我正在编写一个C#程序,需要按以下指定结构保存和加载对象列表:

SIGNATURE\r\n
SIZE\r\n
OBJECT_DATA\r\n
SIZE\r\n
OBJECT_DATA\r\n
\r\n
  • SIGNATURE:带私钥的原始字符串,用来区分我的数据文件和其他文件
  • SIZE:整数值,表示OBJECT_DATA占用的字节数
  • OBJECT_DATA:存储数据的类实例
已尝试方案

StackOverflow上找到的方案几乎都用BinaryFormatter,但它不安全不能用。我也了解了protobuf-net,但不知道怎么应用到我的场景里。

我的思路

序列化流程(输入/输出为二进制文件)

  1. 打开输出文件并写入signature
  2. 遍历源对象列表
    • 计算每个项的大小并写入文件
    • 将对象数据写入二进制文件(这一步还不清楚怎么做)
    • 写入\r\n标记该项结束

反序列化流程

  1. 打开输入文件并读取Signature
  2. 若Signature与密钥匹配:
    • 循环至文件末尾
      • 读取SIZE值
      • 读取对象并创建其类实例
更新信息

OBJECT_DATA是以下抽象类的实例:

public abstract class IShape
{
    public State Configuration { get; set; }
    public abstract string Name { get; }
    public List<Point> Points { get; set; } = [];
    public abstract UIElement Draw();
    public abstract IShape Clone();
    public BitmapImage Preview { get; set; }
}

State Configuration的结构如下:

public class State
{
    public DoubleCollection StrokeDashArray { get; set; }
    public SolidColorBrush Stroke { get; set; }
    public SolidColorBrush Fill { get; set; }
    public double StrokeThickness { get; set; }
}

我觉得Protobuf-net是把对象序列化到Stream的好方法,但我的类包含System.Windows.Media里的类型,不确定能不能用它序列化。


解决方案

一、适配WPF类型的自定义转换器

protobuf-net默认不支持System.Windows.Media下的类型,需要为这些类型编写序列化/反序列化转换器:

  1. SolidColorBrush转换器
public class SolidColorBrushConverter : ITypeConverter<SolidColorBrush, uint>
{
    public uint Convert(SolidColorBrush value)
    {
        return value.Color.R << 16 | value.Color.G << 8 | value.Color.B;
    }

    public SolidColorBrush ConvertBack(uint value)
    {
        var color = Color.FromRgb((byte)(value >> 16), (byte)(value >> 8), (byte)value);
        return new SolidColorBrush(color);
    }
}
  1. DoubleCollection转换器
public class DoubleCollectionConverter : ITypeConverter<DoubleCollection, List<double>>
{
    public List<double> Convert(DoubleCollection value)
    {
        return value?.Cast<double>().ToList() ?? new List<double>();
    }

    public DoubleCollection ConvertBack(List<double> value)
    {
        return new DoubleCollection(value);
    }
}
  1. BitmapImage转换器
public class BitmapImageConverter : ITypeConverter<BitmapImage, byte[]>
{
    public byte[] Convert(BitmapImage value)
    {
        if (value == null) return null;
        using (var ms = new MemoryStream())
        {
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(value));
            encoder.Save(ms);
            return ms.ToArray();
        }
    }

    public BitmapImage ConvertBack(byte[] value)
    {
        if (value == null) return null;
        using (var ms = new MemoryStream(value))
        {
            var image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.StreamSource = ms;
            image.EndInit();
            return image;
        }
    }
}

二、标记可序列化类

为抽象类和子类添加protobuf-net特性,抽象类需注册所有子类:

[ProtoContract]
[ProtoInclude(1, typeof(Circle))] // 替换为你的IShape实际子类
[ProtoInclude(2, typeof(Rectangle))]
public abstract class IShape
{
    [ProtoMember(1)]
    public State Configuration { get; set; }

    [ProtoMember(2)]
    public abstract string Name { get; }

    [ProtoMember(3)]
    public List<Point> Points { get; set; } = [];

    // 抽象方法无需序列化,标记忽略
    [ProtoIgnore]
    public abstract UIElement Draw();
    [ProtoIgnore]
    public abstract IShape Clone();

    [ProtoMember(4, Converter = typeof(BitmapImageConverter))]
    public BitmapImage Preview { get; set; }
}

[ProtoContract]
public class State
{
    [ProtoMember(1, Converter = typeof(DoubleCollectionConverter))]
    public DoubleCollection StrokeDashArray { get; set; }

    [ProtoMember(2, Converter = typeof(SolidColorBrushConverter))]
    public SolidColorBrush Stroke { get; set; }

    [ProtoMember(3, Converter = typeof(SolidColorBrushConverter))]
    public SolidColorBrush Fill { get; set; }

    [ProtoMember(4)]
    public double StrokeThickness { get; set; }
}

三、实现指定格式的序列化/反序列化逻辑

1. 序列化方法

public static void SaveShapes(List<IShape> shapes, string filePath, string signature)
{
    using (var fs = new FileStream(filePath, FileMode.Create))
    using (var writer = new BinaryWriter(fs, Encoding.UTF8, leaveOpen: true))
    {
        // 写入签名
        writer.Write(signature);
        writer.Write("\r\n");

        foreach (var shape in shapes)
        {
            // 序列化对象到内存流,计算字节大小
            using (var ms = new MemoryStream())
            {
                Serializer.Serialize(ms, shape);
                var size = (int)ms.Length;

                // 写入SIZE和换行符
                writer.Write(size.ToString());
                writer.Write("\r\n");

                // 写入对象数据
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(fs);

                // 写入项结束标记
                writer.Write("\r\n");
            }
        }

        // 写入文件结束标记
        writer.Write("\r\n");
    }
}

2. 反序列化方法

public static List<IShape> LoadShapes(string filePath, string expectedSignature)
{
    var shapes = new List<IShape>();

    using (var fs = new FileStream(filePath, FileMode.Open))
    using (var reader = new BinaryReader(fs, Encoding.UTF8, leaveOpen: true))
    {
        // 验证签名
        var signatureLine = ReadLine(reader);
        if (signatureLine != expectedSignature)
        {
            throw new InvalidDataException("文件签名不匹配");
        }

        while (true)
        {
            // 读取SIZE行
            var sizeLine = ReadLine(reader);
            if (string.IsNullOrEmpty(sizeLine)) break;

            if (!int.TryParse(sizeLine, out var size))
            {
                throw new InvalidDataException("无效的SIZE值");
            }

            // 读取对象数据
            var buffer = new byte[size];
            var bytesRead = reader.Read(buffer, 0, size);
            if (bytesRead != size)
            {
                throw new InvalidDataException("读取对象数据不完整");
            }

            // 反序列化对象
            using (var ms = new MemoryStream(buffer))
            {
                var shape = Serializer.Deserialize<IShape>(ms);
                shapes.Add(shape);
            }

            // 跳过项结束的\r\n
            ReadLine(reader);
        }
    }

    return shapes;
}

// 辅助方法:读取一行(直到\r\n)
private static string ReadLine(BinaryReader reader)
{
    var sb = new StringBuilder();
    while (true)
    {
        int b;
        try
        {
            b = reader.ReadByte();
        }
        catch (EndOfStreamException)
        {
            break;
        }
        if (b == '\r')
        {
            reader.ReadByte(); // 跳过\n
            break;
        }
        sb.Append((char)b);
    }
    return sb.ToString();
}

四、注意事项

  • 所有IShape的子类必须添加[ProtoContract]特性,并在抽象类的[ProtoInclude]中注册
  • System.Windows.Point类型protobuf-net默认支持,无需额外处理
  • 自定义转换器可按需调整(比如BitmapImage改用JPEG编码减少文件体积)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 01:10:01