C# 生成コード ガイド

Protocol Buffers コンパイラが proto3 構文を使用するプロトコル定義に対して生成する C# コードが正確に記述されています。

このドキュメントを読む前に、proto3 言語ガイドを読む必要があります。

コンパイラの呼び出し

Protocol Buffers コンパイラは、`--csharp_out` コマンドラインフラグを指定して呼び出されると、C# 出力を生成します。`--csharp_out` オプションのパラメーターは、コンパイラが C# 出力を書き込むディレクトリですが、他のオプションによっては、コンパイラが指定されたディレクトリのサブディレクトリを作成する場合があります。コンパイラは、各 `.proto` ファイル入力に対して単一のソースファイルを作成し、デフォルトでは拡張子が `.cs` になりますが、コンパイラオプションで設定可能です。

C# コードジェネレーターは `proto3` メッセージのみをサポートしています。各 `.proto` ファイルが次の宣言で始まることを確認してください。

syntax = "proto3";

C# 固有のオプション

Protocol Buffers コンパイラに追加の C# オプションを `--csharp_opt` コマンドラインフラグを使用して指定できます。サポートされているオプションは次のとおりです。

  • file_extension: 生成されたコードのファイル拡張子を設定します。デフォルトは `.cs` ですが、ファイルが生成されたコードであることを示すために `.g.cs` を使用するのが一般的です。

  • base_namespace: このオプションが指定されている場合、ジェネレーターは生成されたクラスの名前空間に対応する生成されたソースコードのディレクトリ階層を作成し、オプションの値を使用して名前空間のどの部分が出力ディレクトリの「ベース」と見なされるべきかを示します。たとえば、次のコマンドラインを使用すると、

    protoc --proto_path=bar --csharp_out=src --csharp_opt=base_namespace=Example player.proto
    

    `player.proto` が `csharp_namespace` オプションとして `Example.Game` を持っている場合、Protocol Buffers コンパイラは `src/Game/Player.cs` ファイルを生成します。このオプションは通常、Visual Studio の C# プロジェクトにおけるデフォルトの名前空間オプションに対応します。このオプションが指定されていても値が空の場合、生成されたファイルで使用される完全な C# 名前空間がディレクトリ階層に使用されます。オプションがまったく指定されていない場合、生成されたファイルは階層が作成されることなく、`--csharp_out` で指定されたディレクトリに単純に書き込まれます。

  • internal_access: このオプションが指定されている場合、ジェネレーターは `public` ではなく `internal` アクセス修飾子を持つ型を作成します。

  • serializable: このオプションが指定されている場合、ジェネレーターは生成されたメッセージクラスに `[Serializable]` 属性を追加します。

複数のオプションは、次の例のようにカンマで区切って指定できます。

protoc --proto_path=src --csharp_out=build/gen --csharp_opt=file_extension=.g.cs,base_namespace=Example,internal_access src/foo.proto

ファイル構造

出力ファイルの名前は、`.proto` ファイル名を Pascal-case に変換し、アンダースコアを単語区切り文字として扱うことで導出されます。したがって、たとえば `player_record.proto` というファイルは `PlayerRecord.cs` という出力ファイルになります(ファイル拡張子は `--csharp_opt` を使用して指定できます。これは上記で示されています)。

生成される各ファイルは、パブリックメンバーに関して次の形式を取ります。(実装はここには示されていません。)

namespace [...]
{
  public static partial class [... descriptor class name ...]
  {
    public static FileDescriptor Descriptor { get; }
  }

  [... Enums ...]
  [... Message classes ...]
}

`namespace` は、ファイルの命名規則と同じ変換規則を使用して、proto の `package` から推論されます。たとえば、`example.high_score` という proto パッケージは、`Example.HighScore` という名前空間になります。特定の .proto のデフォルトの生成された名前空間は、`csharp_namespace` ファイルオプションを使用してオーバーライドできます。

各トップレベルの enum とメッセージは、名前空間のメンバーとして enum またはクラスが宣言されます。さらに、ファイル記述子用に常に単一の静的パーシャルクラスが生成されます。これはリフレクションベースの操作に使用されます。記述子クラスには、拡張子なしでファイルと同じ名前が付けられます。ただし、同じ名前のメッセージがある場合(これは非常によくあることですが)、記述子クラスはメッセージとの衝突を避けるためにネストされた `Proto` 名前空間に配置されます。

これらすべての規則の例として、Protocol Buffers の一部として提供される `timestamp.proto` ファイルを考えてみましょう。`timestamp.proto` の短縮版は次のようになります。

syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";

message Timestamp { ... }

生成された `Timestamp.cs` ファイルは次の構造になります。

namespace Google.Protobuf.WellKnownTypes
{
  namespace Proto
  {
    public static partial class Timestamp
    {
      public static FileDescriptor Descriptor { get; }
    }
  }

  public sealed partial class Timestamp : IMessage<Timestamp>
  {
    [...]
  }
}

メッセージ

簡単なメッセージ宣言を考えると

message Foo {}

Protocol Buffers コンパイラは、`IMessage` インターフェースを実装する `Foo` という名前の sealed、partial クラスを生成します。以下にメンバー宣言とともに示します。詳細については、インラインコメントを参照してください。

public sealed partial class Foo : IMessage<Foo>
{
  // Static properties for parsing and reflection
  public static MessageParser<Foo> Parser { get; }
  public static MessageDescriptor Descriptor { get; }

  // Explicit implementation of IMessage.Descriptor, to avoid conflicting with
  // the static Descriptor property. Typically the static property is used when
  // referring to a type known at compile time, and the instance property is used
  // when referring to an arbitrary message, such as during JSON serialization.
  MessageDescriptor IMessage.Descriptor { get; }

  // Parameterless constructor which calls the OnConstruction partial method if provided.
  public Foo();
  // Deep-cloning constructor
  public Foo(Foo);
  // Partial method which can be implemented in manually-written code for the same class, to provide
  // a hook for code which should be run whenever an instance is constructed.
  partial void OnConstruction();

  // Implementation of IDeepCloneable<T>.Clone(); creates a deep clone of this message.
  public Foo Clone();

  // Standard equality handling; note that IMessage<T> extends IEquatable<T>
  public override bool Equals(object other);
  public bool Equals(Foo other);
  public override int GetHashCode();

  // Converts the message to a JSON representation
  public override string ToString();

  // Serializes the message to the protobuf binary format
  public void WriteTo(CodedOutputStream output);
  // Calculates the size of the message in protobuf binary format
  public int CalculateSize();

  // Merges the contents of the given message into this one. Typically
  // used by generated code and message parsers.
  public void MergeFrom(Foo other);

  // Merges the contents of the given protobuf binary format stream
  // into this message. Typically used by generated code and message parsers.
  public void MergeFrom(CodedInputStream input);
}

これらのメンバーはすべて常に存在することに注意してください。`optimize_for` オプションは C# コードジェネレーターの出力には影響しません。

ネストされた型

メッセージは別のメッセージ内で宣言できます。例:

message Foo {
  message Bar {
  }
}

この場合、またはメッセージがネストされた enum を含む場合、コンパイラはネストされた `Types` クラスを生成し、次に `Types` クラス内に `Bar` クラスを生成するため、完全な生成コードは次のようになります。

namespace [...]
{
  public sealed partial class Foo : IMessage<Foo>
  {
    public static partial class Types
    {
      public sealed partial class Bar : IMessage<Bar> { ... }
    }
  }
}

中間的な `Types` クラスは不便ですが、ネストされた型がメッセージ内に対応するフィールドを持つという一般的なシナリオに対処するために必要です。そうしないと、同じクラス内に同じ名前のプロパティと型が両方ともネストされてしまい、それは無効な C# になります。

フィールド

Protocol Buffers コンパイラは、メッセージ内で定義された各フィールドに対して C# プロパティを生成します。プロパティの正確な性質は、フィールドの性質、つまりその型、およびそれが単数、繰り返し、またはマップフィールドであるかによって異なります。

単数フィールド

すべての単数フィールドは読み書き可能なプロパティを生成します。`string` または `bytes` フィールドは、null 値が指定された場合に `ArgumentNullException` を生成します。明示的に設定されていないフィールドから値を取得すると、空の文字列または `ByteString` が返されます。メッセージフィールドは null 値に設定できます。これは実質的にフィールドをクリアすることと同じです。これは、値をメッセージ型の「空の」インスタンスに設定することとは異なります。

繰り返しフィールド

各繰り返しフィールドは、`T` がフィールドの要素型である `Google.Protobuf.Collections.RepeatedField` 型の読み取り専用プロパティを生成します。ほとんどの場合、これは `List` のように動作しますが、項目のコレクションを一括で追加できる追加の `Add` オーバーロードがあります。これは、オブジェクト初期化子で繰り返しフィールドを設定するときに便利です。さらに、`RepeatedField` はシリアル化、デシリアル化、クローン作成を直接サポートしていますが、これは通常、手動で記述されたアプリケーションコードではなく、生成されたコードによって使用されます。

繰り返しフィールドは、メッセージ型であっても null 値を含めることはできません。ただし、以下で説明する null 許容ラッパー型は例外です。

マップフィールド

各マップフィールドは、`TKey` がフィールドのキー型、`TValue` がフィールドの値型である `Google.Protobuf.Collections.MapField` 型の読み取り専用プロパティを生成します。ほとんどの場合、これは `Dictionary` のように動作しますが、別の辞書を一括で追加できる追加の `Add` オーバーロードがあります。これは、オブジェクト初期化子で繰り返しフィールドを設定するときに便利です。さらに、`MapField` はシリアル化、デシリアル化、クローン作成を直接サポートしていますが、これは通常、手動で記述されたアプリケーションコードではなく、生成されたコードによって使用されます。マップ内のキーは null を許可しません。対応する単数フィールド型が null 値をサポートする場合、値は null になる可能性があります。

Oneof フィールド

oneof 内の各フィールドには、通常の単数フィールドと同様に、個別のプロパティがあります。しかし、コンパイラは、enum 内のどのフィールドが設定されたかを判断するための追加のプロパティと、oneof をクリアするための enum およびメソッドも生成します。たとえば、この oneof フィールド定義の場合、

oneof avatar {
  string image_url = 1;
  bytes image_data = 2;
}

コンパイラはこれらのパブリックメンバーを生成します。

enum AvatarOneofCase
{
  None = 0,
  ImageUrl = 1,
  ImageData = 2
}

public AvatarOneofCase AvatarCase { get; }
public void ClearAvatar();
public string ImageUrl { get; set; }
public ByteString ImageData { get; set; }

プロパティが現在の oneof の「ケース」である場合、そのプロパティを取得すると、そのプロパティに設定された値が返されます。そうでない場合、プロパティを取得すると、そのプロパティの型のデフォルト値が返されます。oneof のメンバーは一度に1つしか設定できません。

oneof の構成要素であるプロパティを設定すると、oneof の報告される「ケース」が変更されます。通常の単数フィールドと同様に、`string` または `bytes` 型の oneof フィールドを null 値に設定することはできません。メッセージ型フィールドを null に設定することは、oneof 固有の `Clear` メソッドを呼び出すことと同じです。

ラッパー型フィールド

proto3 の既知の型のほとんどはコード生成に影響しませんが、ラッパー型(`StringWrapper`、`Int32Wrapper` など)はプロパティの型と動作を変更します。

C# 値型に対応するすべてのラッパー型(`Int32Wrapper`、`DoubleWrapper`、`BoolWrapper` など)は、対応する null 許容でない型が `T` となる `Nullable` にマッピングされます。たとえば、`DoubleValue` 型のフィールドは、`Nullable` 型の C# プロパティになります。

`StringWrapper` または `BytesWrapper` 型のフィールドは、`string` および `ByteString` 型の C# プロパティを生成しますが、デフォルト値は null であり、プロパティ値として null を設定できます。

すべてのラッパー型において、繰り返しフィールドでは null 値は許可されませんが、マップエントリの値としては許可されます。

列挙型

次のような列挙型の定義を考えると、

enum Color {
  COLOR_UNSPECIFIED = 0;
  COLOR_RED = 1;
  COLOR_GREEN = 5;
  COLOR_BLUE = 1234;
}

Protocol Buffers コンパイラは、同じ値のセットを持つ `Color` という C# enum 型を生成します。enum 値の名前は、C# 開発者にとってより慣用的なものになるように変換されます。

  • 元の名前が enum 名自体の大文字形式で始まる場合、それは削除されます。
  • 結果は Pascal Case に変換されます。

したがって、上記の `Color` proto enum は次の C# コードになります。

enum Color
{
  Unspecified = 0,
  Red = 1,
  Green = 5,
  Blue = 1234
}

この名前変換は、メッセージの JSON 表現内で使用されるテキストには影響しません。

`.proto` 言語では、複数の enum シンボルが同じ数値を持つことができることに注意してください。同じ数値を持つシンボルは同義語です。これらは C# でもまったく同じように、同じ数値に対応する複数の名前で表現されます。

ネストされていない列挙型は、新しい名前空間メンバーとして C# enum が生成される結果となります。ネストされた列挙型は、その列挙型がネストされているメッセージに対応するクラス内の `Types` ネストクラス内に C# enum が生成される結果となります。

サービス

C# コードジェネレーターはサービスを完全に無視します。