从.NET Framework迁移至.NET6:protobuf-net图片序列化向后兼容方案
.NET6迁移中protobuf-net读取旧文件的兼容性处理
背景
原本基于.NET Framework使用protobuf-net v2.4.0,通过自定义代理类SystemDrawingImageSurrogate存储System.Drawing.Image类型的Photo字段。现在迁移至.NET6,需将PersonWithPhotoSurrogate类中的Photo字段从System.Drawing.Image改为byte[],需要保证能正常读取旧版本存储的protobuf文件。
旧代理类代码
internal class SystemDrawingImageSurrogate { public SystemDrawingImageSurrogate(byte[] data) { Data = data; } public byte[] Data; public static implicit operator System.Drawing.Image(SystemDrawingImageSurrogate surrogate) { if (surrogate == null || surrogate.Data == null) return null; MemoryStream stream = new MemoryStream(surrogate.Data); var bitmap = new System.Drawing.Bitmap(stream); return bitmap; } public static implicit operator SystemDrawingImageSurrogate(System.Drawing.Image source) { if (source == null) return null; byte[] byteImage = (byte[])new ImageConverter().ConvertTo(source, typeof(byte[])); return new SystemDrawingImageSurrogate(byteImage); } }
旧数据类代码
public class PersonWithPhotoSurrogate : PersonSurrogate { public PersonWithPhotoSurrogate(PersonWithPhoto pwp) : base(pwp) { } public int PictureWidth; public int PictureHeight; public System.Drawing.Image Photo; } public class FileBodySurrogate { public FileBodySurrogate(FileBody fileBody) { } public List<Person> People; }
修改后的新数据类
public class PersonWithPhotoSurrogate : PersonSurrogate { public PersonWithPhotoSurrogate(PersonWithPhoto pwp) : base(pwp) { } public int PictureWidth; public int PictureHeight; public byte[] Photo; }
兼容性解决方案
核心思路:让protobuf-net读取旧数据时,能将原SystemDrawingImageSurrogate结构直接映射到新的byte[] Photo字段,同时保证新写入的数据字段编号与旧版本一致。
方法一:保留旧代理类并添加转换
此方法最简单,利用原有代理类的结构,添加到byte[]的隐式转换,让protobuf-net自动识别转换逻辑。
- 保留并修改旧代理类
给SystemDrawingImageSurrogate添加到byte[]的隐式转换,同时标记为过时(可选,用于代码提示):
[Obsolete("仅用于兼容旧protobuf存储数据,请勿在新代码中使用")] internal class SystemDrawingImageSurrogate { public SystemDrawingImageSurrogate(byte[] data) { Data = data; } public byte[] Data; // 原有转换方法保留 public static implicit operator System.Drawing.Image(SystemDrawingImageSurrogate surrogate) { if (surrogate == null || surrogate.Data == null) return null; MemoryStream stream = new MemoryStream(surrogate.Data); var bitmap = new System.Drawing.Bitmap(stream); return bitmap; } public static implicit operator SystemDrawingImageSurrogate(System.Drawing.Image source) { if (source == null) return null; byte[] byteImage = (byte[])new ImageConverter().ConvertTo(source, typeof(byte[])); return new SystemDrawingImageSurrogate(byteImage); } // 新增:从代理类到byte[]的隐式转换 public static implicit operator byte[] (SystemDrawingImageSurrogate surrogate) { return surrogate?.Data; } }
- 给新字段指定一致的protobuf编号
在新的PersonWithPhotoSurrogate类中,给所有字段添加[ProtoMember]属性,确保Photo字段的编号和旧版本完全一致(如果旧代码没指定编号,protobuf-net会按字段顺序自动分配,需确认旧编号,比如旧Photo是第3个字段则用3):
public class PersonWithPhotoSurrogate : PersonSurrogate { public PersonWithPhotoSurrogate(PersonWithPhoto pwp) : base(pwp) { } [ProtoMember(1)] public int PictureWidth; [ProtoMember(2)] public int PictureHeight; [ProtoMember(3)] // 必须与旧版本中Photo字段的protobuf编号一致 public byte[] Photo; }
- 初始化protobuf模型
确保protobuf-net的运行时模型能识别相关类型,默认模型会自动处理,但如果有自定义模型,需注册:
var model = RuntimeTypeModel.Default; // 注册代理类和新数据类 model.Add(typeof(SystemDrawingImageSurrogate), false).Add("Data"); model.Add(typeof(PersonWithPhotoSurrogate), false) .Add("PictureWidth", "PictureHeight", "Photo");
方法二:使用占位类模拟旧结构(无需保留旧代理类)
如果不想保留旧代理类,可以创建一个仅用于解析的占位类,模拟旧代理类的结构,实现到byte[]的转换。
- 创建占位类
// 仅用于解析旧protobuf数据的占位类 internal class PhotoSurrogatePlaceholder { [ProtoMember(1)] // 对应旧代理类的Data字段编号 public byte[] Data; // 转换为byte[]的隐式转换 public static implicit operator byte[] (PhotoSurrogatePlaceholder placeholder) { return placeholder?.Data; } }
- 配置protobuf模型
给新的Photo字段指定旧编号,并设置占位类作为解析代理:
var model = RuntimeTypeModel.Default; var personType = model.Add(typeof(PersonWithPhotoSurrogate), false); // 注册字段并指定编号 personType.Add(1, "PictureWidth"); personType.Add(2, "PictureHeight"); // 配置Photo字段使用占位类解析旧数据 var photoField = personType.AddField(3, "Photo"); // 编号与旧版本一致 photoField.SetSurrogate(typeof(PhotoSurrogatePlaceholder));
验证要点
- 确认旧版本中
Photo字段的protobuf编号(可通过旧代码的[ProtoMember]属性查看,或用protobuf反序列化工具分析旧文件)。 - 测试读取旧文件:确保能正常将旧数据中的图片字节数组赋值给新的
byte[] Photo字段。 - 测试写入新文件:新写入的
Photo字段会直接存储为字节数组,后续读取无问题。
内容的提问来源于stack exchange,提问作者ilCosmico
相关产品推荐
相关产品推荐

