1

I want to use protobuf to use for the protocol over a socket connection.

My questions are regarding inheritance.

Say I have the following classes in my project:

  1. Animal
  2. Cat (inherits from Animal)
  3. Dog (inherits from Animal)

Let's say:

  1. Animal inherits from Creature which is from a class in a DLL that I cannot modify the code of (let's say it's a 3rd party library).
  2. Cat has 10 fields which I give the attributes ProtoMember 1 to 10 for.
  3. Dog as 12 fields so I give that ProtoMember 1 to 12.
  4. Animal has 5 fields so I give that ProtoMember 1 to 5.

So far so good.

In order to deal with the inheritance, let's say I use the following attribute on Cat:

[ProtoInclude(11, typeof(Pet))]

And on Dog I use:

[ProtoInclude(13, typeof(Pet))]

And on Animal use:

[ProtoInclude(6, typeof(Creature))]

Questions:

  1. Are these numbers I've used so far all valid? If not, what should they be and what's the reason for it?
  2. Should I be giving the numbers in ProtoInclude a gap (e.g. 111, 113 and 106) so that it allows for new fields to be added to those classes? Or do I keep the number series compact and adjust in future as and when needed?

So to deal with the inheritance of Creature (which code is not in my project), I believe I have to use Runtime Type declaration (as mentioned here: protobuf-net inheritance)

I'm not quite sure what statements I would need for this example, also where do these statements need to be placed within my project?

Any help will be greatly appreciated. Thank you.

millie
  • 2,642
  • 10
  • 39
  • 58

1 Answers1

1

[ProtoInclude] works from base-class to sub-class - you need to annotate the base type - so: it would be Pet that needed to declare [ProtoInclude(...)] markers for Cat and Dog. Likewise, Creature would need to declare that it expects Animal. Obviously this is a problem if you don't control Creature, but this can be configured via RuntimeTypeModel at runtime if that is a problem. Personally, I wouldn't recommend using a type that you don't control in a serialization hierarchy.

But to your questions:

  1. it doesn't matter, as long as it doesn't conflict with other numbers on the declaring type; lower is cheaper (12 is cheaper to encode than 34134923)
  2. entirely up to you; it doesn't matter if regular fields and sub-type fields are interleaved, so it isn't a problem to have
[ProtoInclude(4, Whatever)]
[ProtoInclude(7, WhateverElse)]
class Foo {
   [ProtoMember(1, ...)] ...
   [ProtoMember(2, ...)] ...
   [ProtoMember(3, ...)] ...
   [ProtoMember(5, ...)] ...
   [ProtoMember(6, ...)] ...
   [ProtoMember(8, ...)] ...
}

but I acknowledge that many people prefer to keep the 2 separate - perhaps

[ProtoInclude(101, Whatever)]
[ProtoInclude(102, WhateverElse)]
class Foo {
   [ProtoMember(1, ...)] ...
   [ProtoMember(2, ...)] ...
   [ProtoMember(3, ...)] ...
   [ProtoMember(4, ...)] ...
   [ProtoMember(5, ...)] ...
   [ProtoMember(6, ...)] ...
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thank you for the reply. Where should the statements for `RuntimeTypeModel` be written? My model classes are within a library DLL project which in turn are used by other library DLL projects and serveral service projects (windows service and web service).? – millie Jul 16 '19 at 09:51
  • @millie and that's why I don't recommend doing it in the first place :) you can make it work well enough if you're one end of the chain (the very bottom or the very top) - but if you're in the middle, it becomes a massive pain. It can be done, but: it will be more pain than it is worth. IMO, seriously consider making every effort to simply not need it - by not mixing the domain model and serialization model. What I mean by that is: when it gets messy, serialize internal types that *you control* - map them to the *public* model **separately**. – Marc Gravell Jul 16 '19 at 16:13