protobuf-net支持可空类型吗?

26

在protobuf-net中是否可能生成可为空的成员?

message ProtoBuf1 {
    optional Int32? databit = 1;
    optional Nullable<bool> databool = 2;
}

4
protobuf-net支持可空类型,但.proto文件不支持;我无法控制.proto文件。 - Marc Gravell
这个回答解决了你的问题吗?在Protobuffers中处理空值 - Serj-Tm
3个回答

15

是的,但如果你是从.proto进行代码生成,它默认不会生成它们。

如果只是C#,当然,您不需要.proto - 只需:

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

    [ProtoMember(2)]
    public float? Bar {get;set;}
}

如果你是从.proto文件工作,你可以考虑复制并编辑csharp.xslt以适应你喜欢的布局。


2
它是否支持所有可空类型,例如可空的日期时间? - mcmillab
如果它可以序列化值类型,那么它也应该能够序列化可空形式。你看到了不同的东西吗? - Marc Gravell
@MarcGravell 对于 DateTime,如何指定默认值? - BaltoStar
@BaltoStar 在属性中表达日期时间非常麻烦,所以 [DefaultValue(...)] 可能不起作用,但你可以使用 [ProtoMember(n)] public DateTime Foo {get;set;} = yourDefault; public bool ShouldSerializeFoo() => Foo != yourDefault; - Marc Gravell
@MarcGravell 请查看 https://dev59.com/4Zvga4cB1Zd3GeqPvwpG - BaltoStar

13

这是我在使用 Google 的 Protobuf .NET API 时针对可空类型的解决方案,该 API 要求使用 Protocol Buffers 版本 3(请注意,这不是使用 Marc Gravell 的 protobuf-net,因此这不是对所提问问题的确切回答)。

NullableInt32.proto 中:

syntax = "proto3";

message NullableInt32 {
  int32 value = 1;
}

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 };
  }
}

这个模式可以用于任何非长度限制的Protobuf纯量值,包括doublefloatint32int64uint32uint64sint32sint64fixed32fixed64sfixed32sfixed64bool


以下是它的工作原理。假设你有一个名为Record的消息,其中包含一个NullableInt32字段,在这个假想的示例中,它是唯一的字段。

Record.proto文件中:

syntax = "proto3";

import "NullableInt32.proto";

message Record {
  NullableInt32 id = 1;
}

使用Google的protoc.exe将此编译为C#后,您可以几乎像使用Nullable<int>一样处理Id属性。

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

最后,让我们看看当Id的值不同时,我们的Record消息将如何通过网络传输。

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
                 //    }

1
这段代码无法编译,“other.Value”不存在。 - Asheh
1
@Asheh,你编译了NullableInt32.proto文件吗? - jordanbtucker
啊,我正在使用注释,所以不需要。 - Asheh

9

Proto 3

导入 "wrappers.proto" 支持可为空的值:

  • 字符串(StringValue),
  • 整数(Int32Value),
  • 布尔值(BoolValue)
  • 等等

支持类型的完整列表 - https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/protobuf-data-types#nullable-types

示例:

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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接