Protobuf スカラー データ型

プロトコル バッファー (Protobuf) では、さまざまなネイティブ スカラー値の型がサポートされます。 次の表に、それらすべておよび同等の C# 型の一覧を示します。

Protobuf 型 C# 型 Notes
double double
float float
int32 int 1
int64 long 1
uint32 uint
uint64 ulong
sint32 int 1
sint64 long 1
fixed32 uint 2
fixed64 ulong 2
sfixed32 int 2
sfixed64 long 2
bool bool
string string 3
bytes ByteString 4

メモ:

  1. int32int64 の標準エンコードは、符号付きの値を操作する場合は非効率になります。 フィールドに負の数値が含まれる可能性がある場合は、代わりに sint32 または sint64 を使用してください。 これらの型は、それぞれ C# の int および long 型にマップされます。
  2. fixed フィールドでは、値にかかわらず常に同じバイト数が使用されます。 この動作により、大きな値に対するシリアル化と逆シリアル化が高速化されます。
  3. Protobuf の string は UTF-8 (または 7 ビット ASCII) でエンコードされます。 エンコードされた長さを 232 より長くすることはできません。
  4. Protobuf ランタイムには、C# の byte[] 配列との間で簡単にマップできる ByteString 型が用意されています。

その他の .NET プリミティブ型

日付と時刻

ネイティブ スカラー型では、C# の DateTimeOffsetDateTimeTimeSpan に相当する日付と時刻の値は提供されません。 これらの型は、Google の "既知の型" 拡張機能を使用して指定できます。 これらの拡張機能によって、サポート対象のプラットフォーム全体で複雑なフィールド型に対するコード生成とランタイム サポートが提供されます。

次の表に日付と時刻の型を示します。

C# 型 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 time = 2;
    google.protobuf.Duration duration = 3;

}  

C# クラスで生成されるプロパティは、.NET の日付と時刻の型ではありません。 このプロパティによって、Google.Protobuf.WellKnownTypes 名前空間の Timestamp および Duration クラスが使用されます。 これらのクラスには、DateTimeOffsetDateTime、および 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
DateTimeOffset time = meeting.Time.ToDateTimeOffset();
TimeSpan? duration = meeting.Duration?.ToTimeSpan();

注意

Timestamp 型は UTC 時刻で動作します。 DateTimeOffset 値では常に、オフセットが 0 となり、DateTime.Kind プロパティは常に DateTimeKind.Utc となります。

System.Guid

Protobuf では、Guid 型 (他のプラットフォームでは UUID と呼ばれます) が直接サポートされていません。 そのための既知の型はありません。

最善の方法は、標準的な 8-4-4-4-12 16 進数形式 (45a9fda3-bd01-47a9-8460-c1cd7484b0b3 など) を使用して、Guid 値を string フィールドとして処理することです。 すべての言語とプラットフォームで、その形式を解析できます。

Guid 値には bytes フィールドを使用しないでください。 "エンディアン" (Wikipedia による定義) に関する問題によって、Protobuf が Java などの他のプラットフォームと対話するときに、動作が不安定になる可能性があります。

null 許容型

C# に対する Protobuf のコード生成には、ネイティブ型 (int32 に対して int など) が使用されます。 そのため、常に値が含まれ、null にすることはできません。

C# コードで int? を使用するなど、明示的な null を必要とする値の場合、Protobuf の "既知の型" には、C# の null 許容型にコンパイルされるラッパーが含まれます。 これらを使用するには、次のように、wrappers.proto.proto ファイルにインポートします。

syntax = "proto3"

import "google/protobuf/wrappers.proto"

message Person {

    ...
    google.protobuf.Int32Value age = 5;

}

Protobuf により、生成されたメッセージ プロパティに対してシンプルな T? (例: int?) が使用されます。

次の表に、ラッパーの型および同等の C# 型の完全な一覧を示します。

C# 型 既知の型のラッパー
double? google.protobuf.DoubleValue
float? google.protobuf.FloatValue
int? google.protobuf.Int32Value
long? google.protobuf.Int64Value
uint? google.protobuf.UInt32Value
ulong? google.protobuf.UInt64Value

既知の型 TimestampDuration は、.NET ではクラスとして表されます。 C# 8 以降では、null 許容参照型を使用できます。 ただし、DateTimeOffset または TimeSpan に変換する場合は、これらの型のプロパティに対して null をチェックすることが重要です。

10 進数

Protobuf では、.NET の decimal 型はネイティブでサポートされません。サポートされるのは、doublefloat のみとなります。 Protobuf プロジェクトでは、標準の Decimal 型を既知の型に追加し、それをサポートする言語とフレームワークをプラットフォームでサポートする可能性について、議論が行われています。 まだ何も実装されていません。

.NET クライアントとサーバー間の安全なシリアル化に使用できる、decimal 型を表すメッセージ定義を作成することができます。 しかし、他のプラットフォームの開発者は、使用されている形式を理解し、独自の処理を実装する必要があります。

Protobuf のカスタム 10 進数型の作成

シンプルな実装は、一部の Google API で使用されている非標準の Money 型の currency フィールドのないものに似ているかもしれません。

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 までの値を表します。 たとえば、decimal 値の 1.5m{ units = 1, nanos = 500_000_000 } として表されます。 これが、この例の nanos フィールドで sfixed32 型が使用されている理由です。これで、より大きな値の場合、int32 よりも効率的にエンコードされるようになります。 units フィールドが負の場合、nanos フィールドも負にする必要があります。

注意

decimal 値をバイト文字列としてエンコードするためのアルゴリズムは他にもいくつかありますが、このメッセージが他のどれよりも理解しやすいものです。 これらの値は、さまざまなプラットフォームにおけるエンディアンの影響を受けません。

この型と BCL の decimal 型の間の変換は、このように 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);
        }
    }
}

重要

このようなカスタム メッセージ型を使用する場合は、常に .proto 内でコメントを使用してドキュメントを作成する "必要があります"。 それにより、他の開発者が独自の言語またはフレームワークで、同等の型との間の変換を実装できるようになります。