Grpc类型

gRPC使用Protobuf作为其接口定义语言(IDL)。Protobuf IDL是一种语言无关的格式,用于指定gRPC服务发送和接收的消息。Protobuf消息在.proto文件中定义。本文档介绍了Protobuf概念如何映射到.NET。

Protobuf讯息

消息是Protobuf中的主要数据传输对象。它们在概念上类似于.NET类。

ProtoBuf复制
 
syntax = "proto3";

option csharp_namespace = "Contoso.Messages";

message Person {
    int32 id = 1;
    string first_name = 2;
    string last_name = 3;
}  

前面的消息定义将三个字段指定为“名称/值”对。像.NET类型的属性一样,每个字段都有一个名称和一个类型。字段类型可以是Protobuf标量值类型,例如int32,或其他消息。

除名称外,消息定义中的每个字段都有唯一的编号。当消息序列化为Protobuf时,字段号用于标识字段。序列化一个小数字比序列化整个字段名称要快。由于字段号标识一个字段,因此在更改字段时务必小心。有关更改Protobuf消息的更多信息,请参见Versioning gRPC services

构建应用程序后,Protobuf工具会从.proto文件生成.NET类型。该Person消息生成一个.NET类:

C#复制
 
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

有关Protobuf消息的更多信息,请参见Protobuf语言指南

标量值类型

Protobuf支持多种本机标量值类型。下表列出了所有它们的等效C#类型:

标量值类型
原虫型C#类型
doubledouble
floatfloat
int32int
int64long
uint32uint
uint64ulong
sint32int
sint64long
fixed32uint
fixed64ulong
sfixed32int
sfixed64long
boolbool
stringstring
bytesByteString

标量值始终具有默认值,不能设置为null。此约束包括stringByteString,它们是C#类。string默认为空字符串值,ByteString默认为空字节值。尝试将它们设置为null会引发错误。

可空包装器类型可用于支持空值。

日期和时间

本机标量类型不提供日期和时间值,等效于.NET的DateTimeOffsetDateTimeTimeSpan。可以使用Protobuf的“知名类型”扩展名来指定这些类型。这些扩展为跨支持的平台的复杂字段类型提供了代码生成和运行时支持。

下表显示了日期和时间类型:

日期和时间
.NET类型Protobuf知名类型
DateTimeOffsetgoogle.protobuf.Timestamp
DateTimegoogle.protobuf.Timestamp
TimeSpangoogle.protobuf.Duration
ProtoBuf复制
 
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日期和时间类型。这些属性使用名称空间中的TimestampDurationGoogle.Protobuf.WellKnownTypes。这些类别转换和从提供的方法DateTimeOffsetDateTimeTimeSpan

C#复制
 
// 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

可空类型

C#的Protobuf代码生成使用本机类型,例如intfor int32。因此,值始终包含在内,不能包含在内null

对于需要显式的值null(例如int?在C#代码中使用),Protobuf的知名类型包括包装器,这些包装器被编译为可为空的C#类型。要使用它们,wrappers.proto.proto像以下代码一样导入文件中:

ProtoBuf复制
 
syntax = "proto3"

import "google/protobuf/wrappers.proto"

message Person {
    // ...
    google.protobuf.Int32Value age = 5;
}

wrappers.proto类型不会在生成的属性中公开。Protobuf会自动将它们映射到C#消息中的相应.NET可空类型。例如,一个google.protobuf.Int32Value字段生成一个int?属性。像string和一样的引用类型属性ByteString不变,但null可以毫无错误地分配给它们。

下表列出了包装器类型及其等效的C#类型的完整列表:

表3
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
stringgoogle.protobuf.StringValue
ByteStringgoogle.protobuf.BytesValue

字节数

Protobuf支持bytes标量值类型的二进制有效负载。C#中生成的属性ByteString用作属性类型。

用于ByteString.CopyFrom(byte[] data)从字节数组创建新实例:

C#复制
 
var data = await File.ReadAllBytesAsync(path);

var payload = new PayloadResponse();
payload.Data = ByteString.CopyFrom(data);

ByteString使用ByteString.Span或直接访问数据ByteString.Memory。或调用ByteString.ToByteArray()以将实例转换回字节数组:

C#复制
 
var payload = await client.GetPayload(new PayloadRequest());

await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());

小数点

Protobuf本身不支持.NETdecimal类型,仅支持doublefloat。Protobuf项目中正在进行有关将标准十进制类型添加到知名类型的可能性的讨论,平台对支持它的语言和框架提供支持。尚未执行任何操作。

可以创建一个消息定义来表示decimal可用于.NET客户端和服务器之间安全序列化的类型。但是其他平台上的开发人员将必须了解所使用的格式并对其实施自己的处理。

为Protobuf创建自定义十进制类型

ProtoBuf复制
 
package CustomTypes;

// Example: 12345.6789 -> { units = 12345, nanos = 678900000 }
message DecimalValue {

    // Whole units part of the amount
    int64 units = 1;

    // Nano units of the amount (10^-9)
    // Must be same sign as units
    sfixed32 nanos = 2;
}

nanos字段表示从0.999_999_999到的值-0.999_999_999。例如,该decimal1.5m将表示为{ units = 1, nanos = 500_000_000 }。这就是nanos本示例中的字段使用sfixed32类型的原因,该类型的编码比较int32大的值更有效。如果该units字段为负,则该nanos字段也应为负。

 注意

还有多种其他算法可将decimal值编码为字节字符串,但是此消息比任何一种消息都更易于理解。值不受不同平台上的big-endian或little-endian的影响。

这种类型和BCLdecimal类型之间的转换可以在C#中实现,如下所示:

C#复制
 
namespace CustomTypes
{
    public partial class DecimalValue
    {
        private const decimal NanoFactor = 1_000_000_000;
        public DecimalValue(long units, int nanos)
        {
            Units = units;
            Nanos = nanos;
        }

        public long Units { get; }
        public int Nanos { get; }

        public static implicit operator decimal(CustomTypes.DecimalValue grpcDecimal)
        {
            return grpcDecimal.Units + grpcDecimal.Nanos / NanoFactor;
        }

        public static implicit operator CustomTypes.DecimalValue(decimal value)
        {
            var units = decimal.ToInt64(value);
            var nanos = decimal.ToInt32((value - units) * NanoFactor);
            return new CustomTypes.DecimalValue(units, nanos);
        }
    }
}

馆藏

清单

Protobuf中的列表是通过repeated在字段上使用prefix关键字指定的。以下示例显示了如何创建列表:

ProtoBuf复制
 
message Person {
    // ...
    repeated string roles = 8;
}

在生成的代码中,repeated字段由Google.Protobuf.Collections.RepeatedField<T>通用类型表示。

C#复制
 
public class Person
{
    // ...
    public RepeatedField<string> Roles { get; }
}

RepeatedField<T>实现IList <T>。因此,您可以使用LINQ查询或将其转换为数组或列表。RepeatedField<T>属性没有公共设置器。项目应添加到现有集合中。

C#复制
 
var person = new Person();

// Add one item.
person.Roles.Add("user");

// Add all items from another collection.
var roles = new [] { "admin", "manager" };
person.Roles.Add(roles);

辞典

.NET IDictionary <TKey,TValue>类型在Protobuf中使用表示map<key_type, value_type>

ProtoBuf复制
 
message Person {
    // ...
    map<string, string> attributes = 9;
}

在生成的.NET代码中,map字段由Google.Protobuf.Collections.MapField<TKey, TValue>通用类型表示。MapField<TKey, TValue>实现IDictionary <TKey,TValue>。像repeated属性一样,map属性没有公共设置器。项目应添加到现有集合中。

C#复制
 
var person = new Person();

// Add one item.
person.Attributes["created_by"] = "James";

// Add all items from another collection.
var attributes = new Dictionary<string, string>
{
    ["last_modified"] = DateTime.UtcNow.ToString()
};
person.Attributes.Add(attributes);

非结构化和条件消息

Protobuf是一种合同优先的消息传递格式。.proto构建应用程序时,必须在文件中指定该应用程序的消息,包括其字段和类型。Protobuf的合同优先设计在强制执行消息内容方面非常出色,但可以限制不需要严格合同的情况:

  • 有效负载未知的消息。例如,一条消息的字段可以包含任何消息。
  • 有条件的消息。例如,从gRPC服务返回的消息可能是成功结果,也可能是错误结果。
  • 动态值。例如,一条消息的字段包含类似于JSON的非结构化值集合。

Protobuf提供语言功能和类型来支持这些方案。

任何

Any类型使您可以将消息用作嵌入式类型,而无需对其进行.proto定义。要使用Any类型,请导入any.proto

ProtoBuf复制
 
import "google/protobuf/any.proto";

message Status {
    string message = 1;
    google.protobuf.Any detail = 2;
}
C#复制
 
// 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<Person>();
    // ...
}

一个

oneof字段是一种语言功能。编译器oneof在生成消息类时会处理关键字。使用oneof指定的响应消息既可以返回一个PersonError可能是这样的:

ProtoBuf复制
 
message Person {
    // ...
}

message Error {
    // ...
}

message ResponseMessage {
  oneof result {
    Error error = 1;
    Person person = 2;
  }
}

在中的字段oneof集必须在整个消息声明独特的场数。

使用时oneof,生成的C#代码包括一个枚举,该枚举指定已设置的字段。您可以测试枚举以查找设置了哪个字段。未设置的字段返回null值或默认值,而不是引发异常。

C#复制
 
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是使用前面讨论的oneof功能的Protobuf知名类型。要使用Value类型,请导入struct.proto

ProtoBuf复制
 
import "google/protobuf/struct.proto";

message Status {
    // ...
    google.protobuf.Value data = 3;
}
C#复制
 
// 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的是JsonFormatterJsonWriter类型可以与任何protobuf的消息中使用。Value特别适合与JSON相互转换。

这是与先前代码等效的JSON:

C#复制
 
// 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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值