Does protobuf-net support nullable types?

前端 未结 3 1438
盖世英雄少女心
盖世英雄少女心 2021-01-04 02:31

Is it possible to generate nullable members in protobuf-net?

message ProtoBuf1 {
    optional Int32? databit = 1;
    optional Nullable databool          


        
相关标签:
3条回答
  • 2021-01-04 02:45

    Proto 3

    Import "wrappers.proto" supports nullable values:

    • string(StringValue),
    • int(Int32Value),
    • bool(BoolValue)
    • and etc

    Full list of supported types - https://docs.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/protobuf-data-types#nullable-types

    Example:

    syntax = "proto3";
    import "google/protobuf/wrappers.proto";
    
    message ProtoPerson {
        google.protobuf.StringValue firstName = 1;
        google.protobuf.StringValue lastName = 2;
        google.protobuf.StringValue address1 = 3;
        google.protobuf.Int32Value age = 4;
    }
    
    0 讨论(0)
  • 2021-01-04 02:50

    Here's my solution for nullable types when using Google's Protobuf .NET API which requires Protocol Buffers version 3. (Note that this is not using Marc Gravell's protobuf-net, so this isn't an exact answer to the question asked.)

    In NullableInt32.proto:

    syntax = "proto3";
    
    message NullableInt32 {
      int32 value = 1;
    }
    

    In NullableInt32Extensions.cs:

    public static class NullableInt32Extensions
    {
      public static bool HasValue(this NullableInt32 source)
      {
        return source != null;
      }
    }
    
    public partial class NullableInt32
    {
      public static implicit operator int? (NullableInt32 other)
      {
        return other == null ? (int?)null : other.Value;
      }
    
      public static implicit operator NullableInt32(int? other)
      {
        return other == null ? null : new NullableInt32 { Value = other.Value };
      }
    }
    

    This pattern can be used for any of the Protobuf non-length delimited scalar values—double, float, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, and bool.


    Here's how all of this works. Say you have a Record message that has a NullableInt32 field, and in this contrived example it's the only field.

    In Record.proto:

    syntax = "proto3";
    
    import "NullableInt32.proto";
    
    message Record {
      NullableInt32 id = 1;
    }
    

    Once this is compiled to C# with Google's protoc.exe, you can treat the Id property almost exactly like a Nullable<int>.

    var r = new Record();
    
    // r.Id is null by default, but we can still call HasValue()
    // because extension methods work on null references.
    r.Id.HasValue(); // => false
    
    // We can explicitly set Id to null.
    r.Id = null;
    
    // We can set Id to a primitive numeric value directly
    // thanks to our implicit conversion operators.
    r.Id = 1;
    
    // We can also use NullableInt32 in any context that expects a
    // Nullable<int>. The signature of the following method is
    // bool Equals(int?, int?).
    Nullable.Equals<int>(r.Id, 1); // => true
    
    // We can explicitly set Id to a NullableInt32.
    r.Id = new NullableInt32 { Value = 1 };
    
    // Just like Nullable<int>, we can get or set the Value of a
    // NullableInt32 directly, but only if it's not null. Otherwise,
    // we'll get a NullReferenceException. Use HasValue() to avoid this.
    if(r.Id.HasValue())
      r.Id.Value.ToString(); // => "1"
    
    // Setting Id to 0 is the same as setting Id to a new
    // NullableInt32 since the default value of int32 is 0.
    // The following expressions are equivalent.
    r.Id = 0;
    r.Id = new NullableInt32();
    r.Id = new NullableInt32 { Value = 0 };
    r.Id.Value = 0; // as long as Id is not null
    

    Finally, let's look at how our Record message will be transfered over the wire with different values for Id.

    var r = new Record();
    
    // When Id is null, Record is empty since it has no other fields.
    // Explicitly setting Id to null will have the same effect as
    // never setting it at all.
    r.Id = null;
    r.ToByteArray(); // => byte[0]
    
    // Since NullableInt32 is a Protobuf message, it's encoded as a
    // length delimited type. Setting Id to 1 will yield four bytes.
    // The first two indicate the type and length of the NullableInt32
    // message, and the last two indicate the type and value held within.
    r.Id = 1;
    r.ToByteArray(); // => byte[] {
                     //      0x0a, // field  = 1, type = 2 (length delimited)
                     //      0x02, // length = 2
                     //      0x08, // field  = 1, type = 0 (varint)
                     //      0x01, // value  = 1
                     //    }
    
    // When Id is set to the default int32 value of 0, only two bytes
    // are needed since default values are not sent over the wire.
    // These two bytes just indicate that an empty NullableInt32 exists.
    r.Id = 0;
    r.ToByteArray(); // => byte[] {
                     //      0x0a, // field  = 1, type = 2 (length delimited)
                     //      0x00, // length = 0
                     //    }
    
    0 讨论(0)
  • 2021-01-04 02:55

    Yes, but it doesn't generate them by default if you are doing codegen from .proto.

    If this is just C#, of course, you don't need a .proto - just:

    [ProtoContract]
    public class ProgoBuf1
    {
        [ProtoMember(1)]
        public int? Foo {get;set;}
    
        [ProtoMember(2)]
        public float? Bar {get;set;}
    }
    

    If you are working from .proto, you could consider copying and editing csharp.xslt to suit your preferred layout.

    0 讨论(0)
提交回复
热议问题