不支持 Protobuf-NET IExtensible 继承的解决方法

Posted

技术标签:

【中文标题】不支持 Protobuf-NET IExtensible 继承的解决方法【英文标题】:Workaround for Protobuf-NET IExtensible Inheritance Not Supported 【发布时间】:2020-04-23 02:32:59 【问题描述】:

我正在使用 Protobuf-NET 反序列化协议缓冲区数据,并且正在处理多个相同但扩展名略有不同的 Proto 文件。基本上每个 Proto 提要 99% 相同,然后提供 1 或 2 个彼此不同的字段作为每个字段的扩展。所以我最终得到了多个 99% 相同的 Proto 类。

从逻辑上讲,我想为生成的 C# proto 文件添加继承,以避免 99% 的冗余代码解析每个提要。问题是我在反序列化时收到以下错误:

System.NotSupportedException
  HResult=0x80131515
  Message=IExtensible is not supported in structs or classes with inheritance
  Source=protobuf-net
  StackTrace:
   at ProtoBuf.Serializers.TypeSerializer..ctor(TypeModel model, Type forType, Int32[] fieldNumbers, IProtoSerializer[] serializers, MethodInfo[] baseCtorCallbacks, Boolean isRootType, Boolean useConstructor, CallbackSet callbacks, Type constructType, MethodInfo factory) in C:\code\protobuf-net\src\protobuf-net\Serializers\TypeSerializer.cs:line 97
   at ProtoBuf.Meta.MetaType.BuildSerializer() in C:\code\protobuf-net\src\protobuf-net\Meta\MetaType.cs:line 480
   at ProtoBuf.Meta.MetaType.get_Serializer() in C:\code\protobuf-net\src\protobuf-net\Meta\MetaType.cs:line 372
   at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) in C:\code\protobuf-net\src\protobuf-net\Meta\RuntimeTypeModel.cs:line 802
   at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) in C:\code\protobuf-net\src\protobuf-net\Meta\TypeModel.cs:line 718
   at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) in C:\code\protobuf-net\src\protobuf-net\Meta\TypeModel.cs:line 590
   at ProtoBuf.Serializer.Deserialize[T](Stream source) in C:\code\protobuf-net\src\protobuf-net\Serializer.cs:line 68

因此,似乎不支持这种情况。现在尚不清楚这是否适用于基类或派生类中的 IExtensible 用法。基类必须保持可扩展,因为我应用了多个扩展,但扩展/派生类不能。

我的代码类似于下面的代码,取自this post。 现在在任何人将其标记为“重复”之前 - 这并不是因为我的问题完全不同。虽然它的 OP 询问 IExtensible 的目的和不实施它的后果,但我询问上述用例的任何可能的解决方法或解决方案。

[ProtoBuf.ProtoInclude(1000, typeof(DerivedClass))]
public partial class BaseClass : ProtoBuf.IExtensible 
 
    ... 
    private IExtension extensionObject;
    IExtension IExtensible.GetExtensionObject(bool createIfMissing)
    
        return Extensible.GetExtensionObject(ref extensionObject, createIfMissing);        
    
    

public partial class DerivedClass : BaseClass, ProtoBuf.IExtensible

    ... 
    private IExtension extensionObject;
    IExtension IExtensible.GetExtensionObject(bool createIfMissing)
    
        return Extensible.GetExtensionObject(ref extensionObject, createIfMissing);        
    
   

var baseObject = new MyClass  ... ;    
DerivedClass derivedObject;
using (var stream = new MemoryStream())

    Serializer.Serialize(stream, baseObject);  // throws runtime exception
    stream.Seek(0, SeekOrigin.Begin);
    derivedObject = Serializer.Deserialize<DerivedClass>(stream);

问题:如何实现上述场景的继承?我愿意修改派生类、您可以建议的任何其他工作,甚至使用另一个 .NET Protobuf 库。

【问题讨论】:

【参考方案1】:

这里的根本问题是有碰撞:

protobuf-net 在 protobuf(没有继承)中实现继承,方法是将其建模为多条消息(每种类型一个)和每个决策点的 oneof 在 protobuf 中,每条消息都可以有扩展名 但现在,protobuf-net 中的扩展实现是每个 object 因此不清楚您要将扩展应用到哪个消息

我曾考虑扩展 API 以使其根据逻辑类型(将映射到消息)跟踪扩展,但这很复杂,需要我花时间来添加。

大多数其他 protobuf 工具根本不支持继承,所以我怀疑你会发现这是一个可行的解决方法。

所以:这就是它不存在的原因;那并不真的。给你一个解决方法,对不起。这里唯一的“修复”是“花时间实现每种类型扩展存储和检索的复杂性”

【讨论】:

感谢 Marc 的回复,很高兴得到作者的明确答复。还要感谢您创建和维护 Protobuf-Net 库。对于我的用例,由于我的问题是冗余解析逻辑而不是多个冗余原型类(您的生成器工具使这部分变得容易),我想我将通过将反序列化消息对象作为“动态”传递给我的解析逻辑来解决这个问题。另一个想法是使用 OneOf 库来打开消息类型,但我认为这会导致代码更长/冗余。 我很好奇是否值得考虑基于基类是原始消息而派生类是扩展的假设来扩展 API。我知道这不会涵盖继承的所有用例,但它会涵盖 Protobuf 扩展的“最常见”用例,遵循只有一条原始消息和许多可能的扩展的想法。具有扩展的继承级别可以限制为 1 个派生类,以使事情更简单。 @AlexVPerl 我不认为这是一个安全的假设。如果我们要在这里更改任何内容,那就是添加一个类型感知 API - 类似于 IInheritableExtensible 的东西,看起来很相似,但在每个方法中都采用 Type

以上是关于不支持 Protobuf-NET IExtensible 继承的解决方法的主要内容,如果未能解决你的问题,请参考以下文章

不支持 Protobuf-NET IExtensible 继承的解决方法

Protobuf-net - 流不支持并发 IO 读取或写入操作

使用protobuf-net继承时如何选择字段号?

Protobuf-net & IL2CPP - System.Reflection.Emit 不受支持

protobuf-net 是不是支持命名元组?

protobuf-net 嵌套类支持?订单注释?