如何用protobuf-net序列化实现ITypedList的已知类型object[]数组?
序列化实现方案
针对你的场景(基于ITypedList的object[]行数据结构),有几种可行的protobuf-net序列化方案,各有优劣,下面逐一分析:
1. 优化版包装类方案(推荐)
你提出的ValueWrapper思路是可行的,但可以通过OneOf特性优化内存占用和序列化体积,避免冗余字段:
[ProtoContract] public enum AvailableTypes { [ProtoEnumValue(0)] Int, [ProtoEnumValue(1)] String, [ProtoEnumValue(2)] Bool } [ProtoContract] public struct ValueWrapper // 用struct减少堆内存分配 { [ProtoMember(1)] public AvailableTypes Type { get; set; } [ProtoOneOf("Value")] [ProtoMember(2)] public int IntValue { get; set; } [ProtoOneOf("Value")] [ProtoMember(3)] public string StringValue { get; set; } [ProtoOneOf("Value")] [ProtoMember(4)] public bool BoolValue { get; set; } // 便捷转换方法 public static implicit operator ValueWrapper(int value) => new() { Type = AvailableTypes.Int, IntValue = value }; public static implicit operator ValueWrapper(string value) => new() { Type = AvailableTypes.String, StringValue = value }; public static implicit operator ValueWrapper(bool value) => new() { Type = AvailableTypes.Bool, BoolValue = value }; public object ToObject() => Type switch { AvailableTypes.Int => IntValue, AvailableTypes.String => StringValue, AvailableTypes.Bool => BoolValue, _ => throw new InvalidOperationException("未知类型") }; }
优势:
- 用
struct替代class,减少堆内存分配,提升性能 ProtoOneOf确保同一时间只有一个值字段被序列化,大幅减小序列化后的体积- 隐式转换方法简化
object[]和ValueWrapper[]之间的映射
转换示例:
// object[]转ValueWrapper[] var valueWrappers = rowArray.Select(obj => (ValueWrapper)obj).ToArray(); // ValueWrapper[]转object[] var originalArray = valueWrappers.Select(wrapper => wrapper.ToObject()).ToArray();
2. 利用RuntimeTypeModel实现多态序列化
如果不想引入包装类,可以通过protobuf-net的RuntimeTypeModel注册所有已知类型,直接序列化object[]:
步骤:
- 注册所有可能的类型(从
ITypedList获取):
var model = RuntimeTypeModel.Default; model.Add(typeof(object), false) .AddSubType(1, typeof(int)) .AddSubType(2, typeof(string)) .AddSubType(3, typeof(bool));
- 直接序列化
object[]:
using var stream = new MemoryStream(); model.Serialize(stream, rowArray); stream.Position = 0; var deserialized = (object[])model.Deserialize(stream, null, typeof(object[]));
优势:
- 无需额外包装类,代码更简洁
- 直接操作原始
object[],避免转换开销
注意事项:
- 必须提前注册所有可能的类型(可通过
ITypedList.GetItemProperties()遍历获取并注册) - 序列化后的体积略大于OneOf包装类方案,因为protobuf-net会为每个对象添加类型标识字段
- 若类型较多,注册和维护成本会增加
3. 基于ITypedList的动态序列化(高性能但复杂)
如果追求极致性能,可以直接针对每个元素的类型调用对应的序列化器,手动构建protobuf消息:
思路:
- 通过
ITypedList获取每个元素的类型 - 利用protobuf-net的
Serializer.NonGeneric.Serialize方法逐个序列化元素 - 序列化时记录每个元素的类型索引,反序列化时按索引还原类型
示例代码片段:
// 提前建立类型与索引的映射字典 private readonly Dictionary<Type, int> _typeToIndex = new() { { typeof(int), 0 }, { typeof(string), 1 }, { typeof(bool), 2 } }; private readonly Dictionary<int, Type> _indexToType = new() { { 0, typeof(int) }, { 1, typeof(string) }, { 2, typeof(bool) } }; // 序列化 using var stream = new MemoryStream(); // 先写入元素数量 Serializer.SerializeWithLengthPrefix(stream, rowArray.Length, PrefixStyle.Base128); foreach (var obj in rowArray) { var typeIndex = _typeToIndex[obj.GetType()]; Serializer.SerializeWithLengthPrefix(stream, typeIndex, PrefixStyle.Base128); Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128); } // 反序列化 stream.Position = 0; var length = Serializer.DeserializeWithLengthPrefix<int>(stream, PrefixStyle.Base128); var result = new object[length]; for (int i = 0; i < length; i++) { var typeIndex = Serializer.DeserializeWithLengthPrefix<int>(stream, PrefixStyle.Base128); var type = _indexToType[typeIndex]; result[i] = Serializer.NonGeneric.DeserializeWithLengthPrefix(type, stream, PrefixStyle.Base128); }
优势:
- 内存分配最少,性能最优
- 完全自定义序列化逻辑,适配复杂场景
劣势:
- 实现复杂度高,需要手动处理类型映射和消息结构
- 代码维护成本高,不易扩展
方案选择建议
- 如果追求开发效率和代码简洁性:选择多态序列化方案(方案2)
- 如果追求序列化体积和内存效率:选择优化版包装类方案(方案1)
- 如果追求极致性能:选择动态序列化方案(方案3)
内容的提问来源于stack exchange,提问作者Wolfgang
相关产品推荐
相关产品推荐

