はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】protobufのカスタムオプションを取得する方法

はじめに

今回はprotobufのカスタムオプションをC#で取り出す方法を紹介したいと思います。
protobuf.dev

// カスタムオプションを定義
extend google.protobuf.MessageOptions {
    string my_option = 50000;
}

message MyMessage {
    // "Hello, world!"をC#上で取り出したい
    option (my_option) = "Hello world!";
    
    string message = 1;
}

カスタムオプションの定義

見落としてるものがあるかもしれませんが、以下のオプションに対してカスタムオプションという形で拡張することができます。

  • EnumOptions
  • EnumValueOptions
  • FieldOptions
  • FileOptions
  • MessageOptions
  • MethodOptions
  • OneofOptions
  • ServiceOptions

例えばMessageOptionsを拡張してみます。

// example.proto
syntax = "proto3";

import "google/protobuf/descriptor.proto";

option csharp_namespace = "Protobuf.Sample";

// メッセージ定義にカスタムオプションを追加
extend google.protobuf.MessageOptions {
    string my_option = 50000;
}

// メッセージ定義
message MyMessage {
    // 上で定義したカスタムオプションを使用
    option (my_option) = "Hello world!";
    
    string message = 1;
}

一応1000以上は利用してOKですが、すでにグローバルで一意な番号を予約がちょこちょこされているようです。自身で定義する場合は50000-99999を利用するのが良いらしいです。
github.com
github.com

// google/protobuf/descriptor.protoの一部抜粋
message MessageOptions {
  optional bool message_set_wire_format = 1 [default=false];
  optional bool no_standard_descriptor_accessor = 2 [default=false];
  optional bool deprecated = 3 [default=false];
  optional bool map_entry = 7;
  reserved 8;  // javalite_serializable
  reserved 9;  // javanano_as_lite
  repeated UninterpretedOption uninterpreted_option = 999;

  // 1000 ~ 536870911をカスタムオプション用に用意
  extensions 1000 to max;
}

One last thing: Since custom options are extensions, they must be assigned field numbers like any other field or extension. In the examples earlier, we have used field numbers in the range 50000-99999. This range is reserved for internal use within individual organizations, so you can use numbers in this range freely for in-house applications.

// DeepL翻訳
最後にもうひとつ、カスタムオプションは拡張機能なので、他のフィールドや拡張機能と同じようにフィールド番号を割り当てる必要があります。先ほどの例では、50000-999999の範囲のフィールド番号を使いました。この範囲は、個々の組織内での内部使用のために予約されているので、社内のアプリケーションではこの範囲の番号を自由に使うことができます。

Language Guide (proto 2) | Protocol Buffers Documentation

コンパイル

protocでコード生成された.csを通してカスタムオプションを取得していくので、コード生成を行っておく必要があります。

$ protoc example.proto --csharp_out="." 

カスタムオプションを取得する

Google.Protobuf.ReflectionDescriptorを通してカスタムオプションを取得します。

  • EnumDescriptor
  • EnumValueDescriptor
  • FieldDescriptor
  • FileDescriptor
  • MessageDescriptor
  • MethodDescriptor
  • OneofDescriptor
  • ServiceDescriptor

protobuf.dev


MessageDescriptor.GetOptionsを実行してオプションを取得した後、MessageOptions.GetExtensionで値を取り出します。
Class MessageOptions (3.27.1)  |  .NET client library  |  Google Cloud

using Google.Protobuf.Reflection;
using Protobuf.Sample;

// MessageDescriptorを取得
MessageDescriptor descriptor = MyMessage.Descriptor;

// オプションを取得
MessageOptions options = descriptor.GetOptions();

// MyMessageからMyOptionを取り出す
// example.proto => ExampleExtensions
if (options.HasExtension(ExampleExtensions.MyOption))
{
    // Hello world!
    Console.WriteLine(options.GetExtension(ExampleExtensions.MyOption));
}