protobuf-net:反序列化扩展关键字到继承层次结构

protobuf-net: deserialise extend keyword to inheritance hierarchy

我有一个使用 protobuf-net 进行继承的特定用例,我还没有找到它(尽管很高兴被重定向到任何有用的答案)。

我必须反序列化来自第三方 protobuf 提要 (GTFS-RT) 的一些对象,并且提供的 .proto 使用 extend 关键字来扩展基本类型(我们也使用它与其他提要),从 objective 的角度来看,这似乎是明智的。但是,我无法让 protobuf-net 将这种格式的提要反序列化为适当的继承层次结构。

例如base.proto定义了一个FeedHeader(在一个叫transit_realtime的包下):

message FeedHeader {
  required string gtfs_realtime_version = 1;

  enum Incrementality {
    FULL_DATASET = 0;
    DIFFERENTIAL = 1;
  }
  optional Incrementality incrementality = 2 [default = FULL_DATASET];

  optional uint64 timestamp = 3;

  extensions 1000 to 1999;
}

第三方扩展 FeedHeader 以包含另一个 属性:

extend transit_realtime.FeedHeader {
 optional NyctFeedHeader nyct_feed_header = 1001;
}

我想将其反序列化为以下 class 层次结构:

namespace Base.GTFS 
{
    [ProtoContract(Name = nameof(FeedHeader))]
    public class FeedHeader
    {
        [ProtoMember(1, IsRequired = true, Name = nameof(gtfs_realtime_version), DataFormat = DataFormat.Default)]
        public string gtfs_realtime_version { get; set; }

        [ProtoMember(2, IsRequired = false, Name = nameof(incrementality), DataFormat = DataFormat.TwosComplement)]
        [DefaultValue(Incrementality.FULL_DATASET)]
        public Incrementality incrementality { get; set; } = Incrementality.FULL_DATASET;

        [ProtoMember(3, IsRequired = false, Name = nameof(timestamp), DataFormat = DataFormat.TwosComplement)]
        [DefaultValue(default(ulong))]
        public ulong timestamp { get; set; } = default(ulong);

        public FeedHeader() { }

        #region Nested Enums
        [ProtoContract(Name = nameof(Incrementality))]
        public enum Incrementality
        {

            [ProtoEnum(Name = nameof(FULL_DATASET), Value = 0)]
            FULL_DATASET = 0,

            [ProtoEnum(Name = nameof(DIFFERENTIAL), Value = 1)]
            DIFFERENTIAL = 1
        }
        #endregion
    }
}

namespace Other.GTFS 
{
    [ProtoContract(Name = nameof(FeedHeader))]
    public class FeedHeader : Base.GTFS.FeedHeader
    {
        /// <summary>
        /// NYCT Subway extensions for the feed header
        /// </summary>
        [ProtoMember(1001, Name = nameof(nyct_feed_header), IsRequired = false, DataFormat = DataFormat.Default)]
        public NyctFeedHeader nyct_feed_header { get; set; } = null;

        public FeedHeader() : base() { }
    }
}

阅读此处和其他地方的其他帖子后,我尝试使用 AddSubType 方法和 AddSurrogate 方法,但发现如果我重写,我只能可靠地反序列化所有字段基础 class 中的所有字段。这看起来非常低效,并且如果(以及何时)基类型发生变化,将会中断。我们还需要对其他提要的基本类型使用序列化,因此我需要一个易于扩展的解决方案。

有没有人知道支持这种情况的任何方法,或者有任何可能有帮助的建议?

proto2 extend and extensions keywords do not correspond to an inheritance hierarchy. Rather, they more closely parallel the partial keyword in c#, allowing a message to be partially defined in one file with additions in another file. From the proto2 language documentation from Google:

Extensions let you declare that a range of field numbers in a message are available for third-party extensions. Other people can then declare new fields for your message type with those numeric tags in their own .proto files without having to edit the original file.

因此,如果我创建文件 Question40863857_1.proto 包含:

package transit_realtime;

message FeedHeader {
  required string gtfs_realtime_version = 1;

  enum Incrementality {
    FULL_DATASET = 0;
    DIFFERENTIAL = 1;
  }
  optional Incrementality incrementality = 2 [default = FULL_DATASET];

  optional uint64 timestamp = 3;

  extensions 1000 to 1999;
}

Question40863857_2.proto包含:

import "Question40863857_1.proto";

// Some random enum since the NyctFeedHeader type wasn't included in the question.
enum NyctFeedHeader {
    Value0 = 0;
    Value1 = 1;
}

extend transit_realtime.FeedHeader {
 optional NyctFeedHeader nyct_feed_header = 1001;
}

然后使用 protobuf-net 的 protogen.exe utility 从它们自动生成一个 c# classes,如下所示:

protogen.exe -i:Question40863857_1.proto -i:Question40863857_2.proto

生成的结果类型:

// Generated from: Question40863857_1.proto
namespace transit_realtime
{
  [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"FeedHeader")]
  public partial class FeedHeader : global::ProtoBuf.IExtensible
  {
    public FeedHeader() {}

    private string _gtfs_realtime_version;
    [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"gtfs_realtime_version", DataFormat = global::ProtoBuf.DataFormat.Default)]
    public string gtfs_realtime_version
    {
      get { return _gtfs_realtime_version; }
      set { _gtfs_realtime_version = value; }
    }
    private transit_realtime.FeedHeader.Incrementality _incrementality = transit_realtime.FeedHeader.Incrementality.FULL_DATASET;
    [global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"incrementality", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    [global::System.ComponentModel.DefaultValue(transit_realtime.FeedHeader.Incrementality.FULL_DATASET)]
    public transit_realtime.FeedHeader.Incrementality incrementality
    {
      get { return _incrementality; }
      set { _incrementality = value; }
    }
    private ulong _timestamp = default(ulong);
    [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"timestamp", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    [global::System.ComponentModel.DefaultValue(default(ulong))]
    public ulong timestamp
    {
      get { return _timestamp; }
      set { _timestamp = value; }
    }
    private NyctFeedHeader _nyct_feed_header = NyctFeedHeader.Value0;
    [global::ProtoBuf.ProtoMember(1001, IsRequired = false, Name=@"nyct_feed_header", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    [global::System.ComponentModel.DefaultValue(NyctFeedHeader.Value0)]
    public NyctFeedHeader nyct_feed_header
    {
      get { return _nyct_feed_header; }
      set { _nyct_feed_header = value; }
    }
    [global::ProtoBuf.ProtoContract(Name=@"Incrementality")]
    public enum Incrementality
    {

      [global::ProtoBuf.ProtoEnum(Name=@"FULL_DATASET", Value=0)]
      FULL_DATASET = 0,

      [global::ProtoBuf.ProtoEnum(Name=@"DIFFERENTIAL", Value=1)]
      DIFFERENTIAL = 1
    }

    private global::ProtoBuf.IExtension extensionObject;
    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
  }

}
// Generated from: Question40863857_2.proto
// Note: requires additional types generated from: Question40863857_1.proto
namespace Question40863857_2
{
    [global::ProtoBuf.ProtoContract(Name=@"NyctFeedHeader")]
    public enum NyctFeedHeader
    {

      [global::ProtoBuf.ProtoEnum(Name=@"Value0", Value=0)]
      Value0 = 0,

      [global::ProtoBuf.ProtoEnum(Name=@"Value1", Value=1)]
      Value1 = 1
    }

}

请注意没有继承层次结构,只有一个 class FeedHeader 包含来自两个 .proto 文件的字段,以及辅助枚举。

事实上,如果你有一套完整的.proto文件,你可以使用protogen.exe为你生成你的c#类型,从而避免这个困难。或者,将 plugin 用于 visual studio。

相反,如果您需要检查 c# 类型 T 指定的合同是否与所需的 .proto 文件匹配,您可以这样做:

Console.WriteLine(RuntimeTypeModel.Default.GetSchema(typeof(T)));

对于 typeof(Other.GTFS.FeedHeader) 这导致:​​

message FeedHeader {
   optional NyctFeedHeader nyct_feed_header = 1001 [default = Value0];
}
enum NyctFeedHeader {
   Value0 = 0;
   Value1 = 1;
}

这显然不是你想要的。