Protobuf
Protobuf 消息
消息定义中的每个字段都有一个唯一的编号。 消息序列化为 Protobuf 时,字段编号用于标识字段。 序列化一个小编号比序列化整个字段名称要快。
标量值类型
Protobuf 类型 | C# 类型 |
---|---|
double |
double |
float |
float |
int32 |
int |
int64 |
long |
uint32 |
uint |
uint64 |
ulong |
sint32 |
int |
sint64 |
long |
fixed32 |
uint |
fixed64 |
ulong |
sfixed32 |
int |
sfixed64 |
long |
bool |
bool |
string |
string |
bytes |
ByteString |
标量值始终具有默认值,并且该默认值不能设置为 null
。 此约束包括 string
和 ByteString
,它们都属于 C# 类。 string
默认为空字符串值,ByteString
默认为空字节值。 尝试将它们设置为 null
会引发错误。
对于需要显式 null
的值,请将 wrappers.proto
导入到 .proto
文件中,如以下代码所示:
syntax = "proto3" import "google/protobuf/wrappers.proto" message Person { // ... google.protobuf.Int32Value age = 5; }
wrappers.proto
类型不会在生成的属性中公开。 Protobuf 会自动将它们映射到 C# 消息中相应的可为 null 的 .NET 类型。 例如,google.protobuf.Int32Value
字段生成 int?
属性。 引用类型属性(如 string
和 ByteString
)保持不变,但可以向它们分配 null
,这不会引发错误。
C# 类型 | 已知类型包装器 |
---|---|
bool? |
google.protobuf.BoolValue |
double? |
google.protobuf.DoubleValue |
float? |
google.protobuf.FloatValue |
int? |
google.protobuf.Int32Value |
long? |
google.protobuf.Int64Value |
uint? |
google.protobuf.UInt32Value |
ulong? |
google.protobuf.UInt64Value |
string |
google.protobuf.StringValue |
ByteString |
google.protobuf.BytesValue |
字节
使用 ByteString.CopyFrom(byte[] data)
从字节数组创建新实例:
var data = await File.ReadAllBytesAsync(path); var payload = new PayloadResponse(); payload.Data = ByteString.CopyFrom(data);
使用 ByteString.Span
或 ByteString.Memory
直接访问 ByteString
数据。 或调用 ByteString.ToByteArray()
将实例转换回字节数组:
var payload = await client.GetPayload(new PayloadRequest()); await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
日期和时间
本机标量类型不提供与 .NET 的 DateTimeOffset、DateTime 和 TimeSpan 等效的日期和时间值。 可使用 Protobuf 的一些“已知类型”扩展来指定这些类型。
.NET 类型 | Protobuf 已知类型 |
---|---|
DateTimeOffset |
google.protobuf.Timestamp |
DateTime |
google.protobuf.Timestamp |
TimeSpan |
google.protobuf.Duration |
syntax = "proto3" import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; message Meeting { string subject = 1; google.protobuf.Timestamp start = 2; google.protobuf.Duration duration = 3; }
C# 类中生成的属性不是 .NET 日期和时间类型。 属性使用 Google.Protobuf.WellKnownTypes
命名空间中的 Timestamp
和 Duration
类。 这些类提供在 DateTimeOffset
、DateTime
和 TimeSpan
之间进行转换的方法。
// Create Timestamp and Duration from .NET DateTimeOffset and TimeSpan. var meeting = new Meeting { Time = Timestamp.FromDateTimeOffset(meetingTime), // also FromDateTime() Duration = Duration.FromTimeSpan(meetingLength) }; // Convert Timestamp and Duration to .NET DateTimeOffset and TimeSpan. var time = meeting.Time.ToDateTimeOffset(); var duration = meeting.Duration?.ToTimeSpan();
Timestamp
类型适用于 UTC 时间。 DateTimeOffset
值的偏移量始终为零,并且 DateTime.Kind
属性始终为 DateTimeKind.Utc
。
列表
Protobuf 中,在字段上使用 repeated
前缀关键字指定列表
message Person { // ... repeated string roles = 8; } public class Person { // ... public RepeatedField<string> Roles { get; } }
字典
.NET IDictionarymap
表示
message Person { // ... map<string, string> attributes = 9; }
在生成的 .NET 代码中,map
字段由 Google.Protobuf.Collections.MapField
泛型类型表示。
无结构的条件消息
Protobuf 是一种协定优先的消息传递格式。 构建应用时,必须在 .proto
文件中指定应用的消息,包括其字段和类型。 Protobuf 的协定优先设计非常适合强制执行消息内容,但可能会限制不需要严格协定的情况:
- 包含未知有效负载的消息。 例如,具有可以包含任何消息的字段的消息。
- 条件消息。 例如,从 gRPC 服务返回的消息可能是成功结果或错误结果。
- 动态值。 例如,具有包含非结构化值集合的字段的消息,类似于 JSON。
Protobuf 提供语言功能和类型来支持这些情况。
任意
利用 Any
类型,可以将消息作为嵌入类型使用,而无需 .proto
定义。 若要使用 Any
类型,请导入 any.proto
。
import "google/protobuf/any.proto"; message Status { string message = 1; google.protobuf.Any detail = 2; } // Create a status with a Person message set to detail. var status = new ErrorStatus(); status.Detail = Any.Pack(new Person { FirstName = "James" }); // Read Person message from detail. if (status.Detail.Is(Person.Descriptor)) { var person = status.Detail.Unpack(); // ... }
Oneof
oneof
字段是一种语言特性。 编译器在生成消息类时处理 oneof
关键字。 使用 oneof
指定可能返回 Person
或 Error
的响应消息可能如下所示:
message Person { // ... } message Error { // ... } message ResponseMessage { oneof result { Error error = 1; Person person = 2; } }
在整个消息声明中,oneof
集内的字段必须具有唯一的字段编号。
使用 oneof
时,生成的 C# 代码包括一个枚举,用于指定哪些字段已设置。 可以测试枚举来查找已设置的字段。 未设置的字段将返回 null
或默认值,而不是引发异常。
var response = await client.GetPersonAsync(new RequestMessage()); switch (response.ResultCase) { case ResponseMessage.ResultOneofCase.Person: HandlePerson(response.Person); break; case ResponseMessage.ResultOneofCase.Error: HandleError(response.Error); break; default: throw new ArgumentException("Unexpected result."); }
“值”
Value
类型表示动态类型的值。 它可以是 null
、数字、字符串、布尔值、值字典 (Struct
) 或值列表 (ValueList
)。 Value
是一个 Protobuf 已知类型,它使用前面讨论的 oneof
功能。 若要使用 Value
类型,请导入 struct.proto
。
import "google/protobuf/struct.proto"; message Status { // ... google.protobuf.Value data = 3; } // Create dynamic values. var status = new Status(); status.Data = Value.FromStruct(new Struct { Fields = { ["enabled"] = Value.ForBoolean(true), ["metadata"] = Value.ForList( Value.FromString("value1"), Value.FromString("value2")) } }); // Read dynamic values. switch (status.Data.KindCase) { case Value.KindOneofCase.StructValue: foreach (var field in status.Data.StructValue.Fields) { // Read struct fields... } break; // ... }
直接使用 Value
可能很冗长。 使用 Value
的替代方法是通过 Protobuf 的内置支持,将消息映射到 JSON。 Protobuf 的 JsonFormatter
和 JsonWriter
类型可用于任何 Protobuf 消息。 Value
特别适用于与 JSON 进行转换。
以下是与之前的代码等效的 JSON:
// Create dynamic values from JSON. var status = new Status(); status.Data = Value.Parser.ParseJson(@"{ ""enabled"": true, ""metadata"": [ ""value1"", ""value2"" ] }"); // Convert dynamic values to JSON. // JSON can be read with a library like System.Text.Json or Newtonsoft.Json var json = JsonFormatter.Default.Format(status.Metadata); var document = JsonDocument.Parse(json);
dotnet-grpc
dotnet-grpc
是一种 .NET Core 全局工具,用于在 .NET gRPC 项目中管理 Protobuf (.proto) 引用。 该工具可以用于添加、刷新、删除和列出 Protobuf 引用。
dotnet tool install -g dotnet-grpc
dotnet-grpc
可以用于将 Protobuf 引用作为
项添加到 .csproj 文件
dotnet-grpc
工具可以:
- 从磁盘上的本地文件创建 Protobuf 引用。
- 从 URL 指定的远程文件创建 Protobuf 引用。
- 确保将正确的 gRPC 包依赖项添加到项目。
例如,将 Grpc.AspNetCore
包添加到 Web 应用。 Grpc.AspNetCore
包含 gRPC 服务器和客户端库以及工具支持。 或者,将 Grpc.Net.Client
、Grpc.Tools
和 Google.Protobuf
包(其中仅包含 gRPC 客户端库和工具支持)添加到控制台应用。
参见
https://docs.microsoft.com/zh-cn/aspnet/core/grpc/dotnet-grpc?view=aspnetcore-3.1