I've several questions on how/where the ID of a [ProtoContract]
should be declared.
Imagine the following code:
[ProtoContract]
[ProtoInclude(100, typeof(SomeClassA))]//1) CAN I USE 1 here?
public abstract class RootClass{
[ProtoMember(1)]
public int NodeId {get;set;}
}
[ProtoContract]
[ProtoInclude(200, typeof(SomeClassC)]//2) Should I declare this here or directly on the RootClass?
//3) Can I use the id 100 here?
//4) Can I use the id 1 here? or member + include share the id?
public class SomeClassA : RootClass{
[ProtoMember(1)]//5) CAN I USE 1 here? Since the parent already use it but it's a different class
public String Name{get;set;}
}
[ProtoContract]
public class SomeClassC : SomeClassA {
[ProtoMember(2)]
public int Count{get;set;}
}
[ProtoContract]
public class SomeClassD : SomeClassA {
[ProtoMember(2)] //6) Can I use 2 here? Since SomeClassC already use it and is a sibling?
public int Count{get;set;}
}
I've put several number with questions:
- CAN I USE 1 here?
- Should I declare this here or directly on the RootClass?
- Can I use the id 100 here?
- Can I use the id 1 here? or member + include share the id?
- CAN I USE 1 here? Since the parent already use it but it's a different class
- Can I use 2 here? Since SomeClassC already use it and is a sibling?
The thing is that we have a huge model with a lot of classes, which all herits from the same object, so I'm trying to figure out to which ID I should take care.
Short version:
- the set of field numbers for a type is the union of the numbers defined against members (fields and properties), and the numbers defined for immediate subtypes (includes)
- the set of field numbers must be unique within that single type - it is not required to consider base types or derived types
Longer version:
The reason for this is that subtypes are essentially mapped as optional fields:
[ProtoContract]
[ProtoInclude(100, typeof(SomeClassA))]
public abstract class RootClass{
[ProtoMember(1)]
public int NodeId {get;set;}
}
[ProtoContract]
[ProtoInclude(200, typeof(SomeClassC)]
public class SomeClassA : RootClass{
[ProtoMember(1)]
public String Name{get;set;}
}
[ProtoContract]
public class SomeClassC : SomeClassA {
[ProtoMember(2)]
public int Count{get;set;}
}
is, in terms of proto2
syntax:
message RootClass {
optional int32 NodeId = 1;
optional SomeClassA _notNamed = 100;
}
message SomeClassA {
optional string Name = 1;
optional SomeClassC _notNamed = 200;
}
message SomeClassC {
optional int32 Count = 2;
}
Note that at most 1 sub-type field will be used, so it can be considered oneof
for the purposes of .proto
. Any fields relating to the sub-type will be included in message SomeClassA
, so there is no conflict with RootClass
and they do not need to be unique. The numbers only need to be unique per message
in the .proto
sense.
To take the specific questions, then:
- no, because that would conflict with
NodeId
- it should be declared on
SomeClassA
; protobuf-net is only expecting immediate descendants, and it keeps the numbering consistent and conveniently readable, since the field number is only required to not conflict with the members of SomeClassA
- yes you can; there is no conflict
- no, because that would conflict with
Name
- yes you can; there is no conflict
yes you can; there is no conflict - although actually protobuf-net won't even think of SomeClassD
as a sibling anyway (it isn't advertised anywhere as an include) - but if there was a [ProtoInclude(201, typeof(SomeClassD))]
on SomeClassA
, then it would be fine. This would change our .proto
to add:
optional SomeClassD _alsoNotNamed = 201;
to message SomeClassA
, and add:
message SomeClassD {
optional int32 Count = 2;
}
Note that protobuf-net doesn't actually generate the .proto
syntax unless you explicitly ask for it (via GetSchema<T>
etc) - I'm including it purely for illustrative purposes in terms of the underlying protobuf concepts.