Home > Mobile >  Protobuf-net - How to use oneof
Protobuf-net - How to use oneof

Time:10-21

I did a quick search about the usage of oneof in Protobuf-net and it appears it's supported as of v2.3.0, but I can't for the life of me find any examples on exactly how one would use it!

My requirements are pretty simple, and maybe this can also be solved with [ProtoInclude] but I'm not quite sure exactly how this would work. I've got the following class:

[ProtoContract]
public class ProgressUIMessage
{
    [ProtoMember(1)]
    public int Id {get; set;}

    [ProtoMember(2)]
    public object Message {get; set;}
}

Where Message can be 1 of 8 different known types. The types do not inherit from each other at all and although the code can be changed, there's nothing all types have in common.

Using Google.Protobuf I'd expect to do something similar to this, where I have a property called Instrument that can be one of two types in the example above and then use InstrumentOneofCase to figure out which type I was given. But how do I achieve the same thing in Protobuf-net?

EDIT: I'll leave the original question as it, but perhaps a better question which more people can relate to is: how would you achieve the same thing as with this MS example in Protobuf-net? Both in terms of writing the class itself and in terms of determining what concrete type the parameter is in the end?

CodePudding user response:

The way to play with this is to take the message from the MS example you cite, and run it through protogen to see what it does - which we can do very conveniently here: https://protogen.marcgravell.com/ (note I'm adding syntax = "proto3"; at the top of the file, which is omitted in the MS example).

This gives us, among other things:

    [global::ProtoBuf.ProtoMember(2, Name = @"stock")]
    public Stock Stock
    {
        get => __pbn__instrument.Is(2) ? ((Stock)__pbn__instrument.Object) : default;
        set => __pbn__instrument = new global::ProtoBuf.DiscriminatedUnionObject(2, value);
    }
    public bool ShouldSerializeStock() => __pbn__instrument.Is(2);
    public void ResetStock() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__instrument, 2);

    private global::ProtoBuf.DiscriminatedUnionObject __pbn__instrument;

    [global::ProtoBuf.ProtoMember(3, Name = @"currency")]
    public Currency Currency
    {
        get => __pbn__instrument.Is(3) ? ((Currency)__pbn__instrument.Object) : default;
        set => __pbn__instrument = new global::ProtoBuf.DiscriminatedUnionObject(3, value);
    }
    public bool ShouldSerializeCurrency() => __pbn__instrument.Is(3);
    public void ResetCurrency() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__instrument, 3);

So we can see that it is basically using conditional serialization built on top of the DiscriminatedUnionObject type. There are actually a bunch of related types named DiscriminatedUnion* - depending on what you need to overlap, but since they're all message types here: DiscriminatedUnionObject works for us.


There's also an optional "oneof should use enum" option (under: oddly, "Options"), which if enabled, also adds:

    public InstrumentOneofCase InstrumentCase => (InstrumentOneofCase)__pbn__instrument.Discriminator;

    public enum InstrumentOneofCase
    {
        None = 0,
        Stock = 2,
        Currency = 3,
    }

Without that, you would have to use the ShouldSerialize*() methods to resolve the active case.

Hopefully that clarifies how oneof can be used with protobuf-net.

  • Related