这不适用于纯静态 protbuf-net 属性。稍微简化一下,假设您从以下内容开始:
namespace V1
{
[ProtoContract]
internal class MarketDataObject
{
[ProtoMember(1)]
public string Id { get; set; }
}
}
并将其重构为以下内容:
namespace V2
{
[ProtoInclude(10, typeof(MarketDataObject))]
[ProtoContract]
internal abstract class LowerStillBaseClass
{
[ProtoMember(1)]
public string LowerStillBaseClassProperty { get; set; }
}
[ProtoContract]
internal class MarketDataObject : LowerStillBaseClass
{
[ProtoMember(1)]
public string Id { get; set; }
}
}
接下来,尝试将一个从 V1 类创建的反序列化为 V2 类。您将失败并出现以下异常:
ProtoBuf.ProtoException: No parameterless constructor found for LowerStillBaseClass
原因这不起作用是类型层次结构是先序列化的,而不是先派生的。要查看这一点,请通过调用 Console.WriteLine(RuntimeTypeModel.Default.GetSchema(type)); 转储每种类型的 protobuf-net 合约,对于 V1.MarketDataObject,我们得到:
message MarketDataObject {
optional string Id = 1;
}
对于V2.MarketDataObject:
message LowerStillBaseClass {
optional string LowerStillBaseClassProperty = 1;
// the following represent sub-types; at most 1 should have a value
optional MarketDataObject MarketDataObject = 10;
}
message MarketDataObject {
optional string Id = 1;
}
MarketDataObject 被编码成message,其基类型字段首先位于顶层,然后派生类型字段递归封装在嵌套可选消息中,其中字段 id 表示其子类型。因此,当 V1 消息反序列化为 V2 对象时,不会遇到子类型字段,不会推断出正确的派生类型,并且派生类型值会丢失。
一种解决方法是避免使用[ProtoInclude(10, typeof(MarketDataObject))],而是使用RuntimeTypeModel API以编程方式在派生类型的契约中填充基类成员:
namespace V3
{
[ProtoContract]
internal abstract class LowerStillBaseClass
{
[ProtoMember(1)]
public string LowerStillBaseClassProperty { get; set; }
}
[ProtoContract]
internal class MarketDataObject : LowerStillBaseClass
{
static MarketDataObject()
{
AddBaseTypeProtoMembers(RuntimeTypeModel.Default);
}
const int BaseTypeIncrement = 11000;
public static void AddBaseTypeProtoMembers(RuntimeTypeModel runtimeTypeModel)
{
var myType = runtimeTypeModel[typeof(MarketDataObject)];
var baseType = runtimeTypeModel[typeof(MarketDataObject).BaseType];
if (!baseType.GetSubtypes().Any(s => s.DerivedType == myType))
{
foreach (var field in baseType.GetFields())
{
myType.Add(field.FieldNumber + BaseTypeIncrement, field.Name);
}
}
}
[ProtoMember(1)]
public string Id { get; set; }
}
}
(这里我在 MarketDataObject 的静态构造函数中填充合同。您可能想在其他地方执行此操作。)V3. 的架构如下所示:
message MarketDataObject {
optional string Id = 1;
optional string LowerStillBaseClassProperty = 11001;
}
此模式与 V1 模式兼容,因此可以将 V1 消息反序列化为 V3 类而不会丢失数据。示例fiddle。
当然,如果您要将成员从 MarketDataObject 移动到 LowerStillBaseClass,则需要确保字段 ID 保持不变。
此解决方法的缺点是您无法反序列化 LowerStillBaseClass 类型的对象,并让 protobuf-net 自动推断正确的派生类型。