How are shared/placed the int of the ProtoMember/P

2019-02-25 04:57发布

问题:

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:

  1. CAN I USE 1 here?
  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?
  5. CAN I USE 1 here? Since the parent already use it but it's a different class
  6. 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.

回答1:

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:

  1. no, because that would conflict with NodeId
  2. 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
  3. yes you can; there is no conflict
  4. no, because that would conflict with Name
  5. yes you can; there is no conflict
  6. 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.