2016-11-29 3 views
3

У меня есть конкретный прецедент для наследования с использованием protobuf-net, который я еще не нашел, хотя здесь все еще включен (хотя он с удовольствием перенаправляется на любые ответы, которые были бы полезны) ,protobuf-net: deserialise extend ключевое слово в иерархию наследования

Я должен десериализовать некоторые объекты из стороннего протокола protobuf (GTFS-RT), а поставляемое .proto использует ключевое слово extend для расширения базовых типов (которые мы также используем с другими каналами), что кажется разумным с объективной точки зрения. Однако я не могу заставить protobuf-net десериализовать каналы в этом формате в соответствующую иерархию наследования.

Например, базовая .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; 
} 

Я хотел бы для десериализации этого в следующую иерархию классов:

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, но обнаружил, что я могу только надежно иметь все десериализованные поля, если я переопределяю все поля в базовом классе. Это кажется крайне неэффективным и будет ломаться, если (и когда) меняются базовые типы. Нам также нужно использовать сериализацию для базовых типов для других каналов, поэтому мне нужно решение, которое будет легко расширяться.

Кто-нибудь знает какой-либо способ поддержать этот сценарий или какие-либо предложения, которые могут вам помочь?

ответ

0

proto2extend и extensions ключевые слова не соответствуют иерархии наследования. Скорее, они более тесно параллельны ключевому слову partial в C#, позволяя частично определить сообщение в одном файле с дополнениями в другом файле. От proto2 language documentation from Google:

Расширения позволяют объявлять, что диапазон номеров полей в сообщении доступен для сторонних расширений. Затем другие люди могут объявлять новые поля для вашего типа сообщений с этими числовыми тегами в своих собственных файлах .proto без необходимости редактировать исходный файл.

Таким образом, если я создаю файл 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; 
} 

Затем автоматически генерировать C# классы из них с помощью Protobuf-Net protogen.exe utility следующим :

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

Полученные типы генерируются:

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

    private string _gtfs_realtime_version; 
    [global::ProtoBuf.ProtoMember(1, IsRequired = true, [email protected]"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, [email protected]"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, [email protected]"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, [email protected]"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([email protected]"Incrementality")] 
    public enum Incrementality 
    { 

     [global::ProtoBuf.ProtoEnum([email protected]"FULL_DATASET", Value=0)] 
     FULL_DATASET = 0, 

     [global::ProtoBuf.ProtoEnum([email protected]"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([email protected]"NyctFeedHeader")] 
    public enum NyctFeedHeader 
    { 

     [global::ProtoBuf.ProtoEnum([email protected]"Value0", Value=0)] 
     Value0 = 0, 

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

} 

Уведомление там нет иерархии наследования, только один класс FeedHeader с полями из обоих .proto файлов, а также с вспомогательными перечислений.

Фактически, если у вас есть полный комплект файлов .proto, вы можете использовать protogen.exe для создания ваших типов C# для вас, тем самым избегая этой трудности. Или воспользуйтесь plugin для визуальной студии.

С другой стороны, если вам нужно проверить, если контракт определяется переменным # типом 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; 
} 

Это явно не то, что вы хотите.

+0

Спасибо за информацию - я понял, что это почти так, как это работает. Это позор, так как у меня есть сервисный уровень, который находится поверх них, который объявляет интерфейс, который использует базовые классы - я думаю, мне нужно будет извлечь необходимые части базовой модели в интерфейсы и обеспечить их применение к частичным сгенерированным классам (упрощается наблюдение, поскольку они являются частичными). – davide