Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to choose a field number when using protobuf-net inheritance?

I'm using protobuf-net for serializing a number of types, some of which are inherited from a base type. I know that the Protocol Buffers spec does not support inheritance, and that the support in protobuf-net is basically a workaround because of that.

Rather than using the protobuf-net attributes I am configuring a custom RuntimeTypeModel, and using the Add and AddSubType methods. What I'm not quite grasping is how I should determine which numbers to use for the field numbers passed to the AddSubType method (aka the number that would be used in a ProtoInclude attribute).

This SO question and several others like it do not really describe how the field numbers are chosen, and indeed I've seen many different variations: 4 & 5; 7 & 8; 101 & 102 & 103; 20; 500; etc. Obviously they're chosen so as not to clash with one another, but how are they chosen? What determines which number to start at?

The following code is a contrived example but it does match my heirarchy (a base Event type that has two derived subtypes).

using System;
using System.Collections.Generic;
using ProtoBuf.Meta;

namespace Test
{
    public sealed class History
    {
        public History()
        {
            Events = new List<Event>();
        }

        public ICollection<Event> Events { get; private set; }
    }

    public enum EventType
    {
        ConcertStarted, ConcertFinished, SongPlayed
    }

    public class Event
    {
        public EventType Type { get; set; }
        public DateTimeOffset Timestamp { get; set; }
    }

    public sealed class Concert : Event
    {
        public string Location { get; set; }
    }

    public sealed class Song : Event
    {
        public string Name { get; set; }
    }

    public static class ModelFactory
    {
        public static RuntimeTypeModel CreateModel()
        {
            RuntimeTypeModel model = TypeModel.Create();
            model.Add(typeof(DateTimeOffset), applyDefaultBehaviour: false)
                .SetSurrogate(typeof(DateTimeOffsetSurrogate));
            model.Add(typeof(History), applyDefaultBehaviour: false)
                .Add("Events");
            model.Add(typeof(Concert), applyDefaultBehaviour: false)
                .Add("Location");
            model.Add(typeof(Song), applyDefaultBehaviour: false)
                .Add("Name");
            model.Add(typeof(Event), applyDefaultBehaviour: false)
                .Add("Type", "Timestamp")
                .AddSubType(???, typeof(Concert))
                .AddSubType(???, typeof(Song));
            return model;
        }
    }
}
like image 619
Steven Rands Avatar asked Mar 26 '15 14:03

Steven Rands


1 Answers

There is no requirement other than:

  • they must be positive integers
  • they can't conflict
  • they must be reliably repeatable (it is important that sub-types and numbers match no matter how many times you restart your app, even if you add additional types, etc)

Other than that: it doesn't matter. Leaving a gap might make it easier to add additional fields to the parent type without accidentally creating conflicts, but: smaller field-numbers are cheaper to serialize, so if possible: prefer small numbers

like image 176
Marc Gravell Avatar answered Nov 11 '22 17:11

Marc Gravell