简体   繁体   中英

Serializing an abstract list in Protobuf-net

I have the following class structure in my application:

[ProtoContract]
public abstract class WebSyncedObject
{
    [ProtoMember(1)]
    public DateTime SystemTime { get; set; }

    [ProtoMember(2)]
    public bool TimeSynchronized { get; set; }

    [ProtoMember(3)]
    public ulong RelativeTime { get; set; }

    [ProtoMember(4)]
    public Guid BootID { get; set; }

    protected WebSyncedObject()
    {
        BootID = BootID.GetBootID();
        if (BootID == Guid.Empty) return;

        TimeSynchronized = Time.TimeSynchronized;
        RelativeTime = Time.RelativeTime;
        SystemTime = DateTime.Now;
    }
}

[ProtoContract]
public class GPSReading : WebSyncedObject
{
    [ProtoMember(1)]
    public DateTime SatelliteTime { get; set; }

    [ProtoMember(2)]
    public decimal Latitude { get; set; }

    [ProtoMember(3)]
    public decimal Longitude { get; set; }

    [ProtoMember(4)]
    public int NumSatellites { get; set; }

    [ProtoMember(5)]
    public decimal SpeedKM { get; set; }
}

[ProtoContract]
public class TemperatureReading : WebSyncedObject
{
    [ProtoMember(1)]
    public decimal Temperature { get; set; }

    [ProtoMember(2)]
    public int NodeID { get; set; }

    [ProtoMember(3)]
    public string ProbeIdentifier { get; set; }
}

I then construct a List<WebSynchedObject> with data of both types, and try to serialize with Protobuf-net when I get the following exception:

InvalidOperationException Unexpected sub-type: Logger.TemperatureReading

I've read about the ProtoInclude attribute, but I don't want to use that as my code needs to be easily extendable, and I'm not sure on how the numbering on the RuntimeTypeModel approach is supposed to work, since I've also seen warnings about generating that automagically.

Is there any way to achieve this whilst making it extendable?

Ultimately, there needs to be a robust, reliable and repeatable way of the library identifying a specific sub-type ( GPSReading , etc) with a unique identifier (a field-number). In many cases, the most convenient way to do that is via attributes. However, if this is not an option, you can also do this at runtime - perhaps reading the identifiers some configuration file. It would not be a good idea to just say (at runtime) "find all the available sub-types, order them alphabetically, and increment them starting at (say) 10", because in a later build you might have added an AltitudeReading , which would change the number of everything , breaking the existing data. But as long as you can define these in a repeatable manner , then all is good. For example, with attributes...

[ProtoInclude(10, typeof(GPSReading))]
[ProtoInclude(11, typeof(TemperatureReading))]
[ProtoInclude(12, typeof(AltitudeReading))]

But you could also do something in a text file, or an xml configuration file... maybe:

<add key="10" type="Some.Namespace.GPSReading"/>
<add key="11" type="Some.Namespace.TemperatureReading"/>
<add key="12" type="Some.Namespace.AltitudeReading"/>

and add you own code that reads the config file, and calls:

int key = int.Parse(element.GetAttributeValue("key"));
Type type = someAssembly.GetType(element.GetAttributeValue("type"));
RuntimeTypeModel.Default[typeof(WebSyncedObject)].AddSubType(key, type);

Again, to emphasize: the important thing is that the numbers associated with each sub-type must be robustly repeatable in the future . As long as you can guarantee that, it is not required to use attributes. But the model does need to know the identifiers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM