はじめに
先日protobuf-netの利用方法についての記事を書いたのですが、今回はGoogle製のライブラリGoogle.ProtoBug
の利用方法について書きたいと思います。
www.nuget.org
github.com
protobuf.dev
サポート
- .NET 4.5+ (net45)
- .NET Standard 1.1 and 2.0 (netstandard1.1 and netstandard2.0)
- .NET 5+ (net50)
環境
- Rider 2023.1.3
- Console Application
- .net7.0
- C#11
インストール方法
Rider上のエクスプローラーから.csproj
を右クリックし、NuGetパッケージの管理
を選択します。
あとはGoogle.ProtoBuf
と検索して右上の+
ボタンを押せばインストール完了です。
おまけ : Riderプラグインの導入
JetBrains
製のProtocol Buffers
プラグインがあるので、これを導入すると便利です。proto2
とproto3
に対応しています。
Provides editor support for Protocol Buffers files.
導入方法は設定画面を開き、プラグイン > Protocol Buffers > インストール
からインストールを行なってください。
使い方
.protoの利用
Google.ProtoBug
を利用するにあたって.proto
ファイルを用いる必要があります。詳細については前回の記事を参照してください。
// sample.proto syntax = "proto3"; option csharp_namespace = "Sample.Messages"; message Person { // Protobugスタイルガイドではフィールド名にunderscore_separated_namesを利用することが推奨 int32 id = 1; string name = 2; Address address = 3; } message Address { string line1 = 1; string line2 = 2; }
.proto
から.cs
を生成するツールを取得する必要があります。簡単な方法としてNuGetからGoogle.ProtoBuf.Tool
をインストールすると、protoc.exe
が手に入ります。ただ私の場合はMac
なので、Release Pagesから最新のprotoc-25.1-osx-universal_binary.zip
をダウンロードして利用します。
$ protoc "対象.protoのパス" --csharp_out="生成先のディレクトリパス"
// サンプル $ protoc "./sample.proto" --csharp_out="./"
Protocol Buffer Basics: C# | Protocol Buffers Documentation
正しく動作すると.cs
ファイルが生成されるはずです。
// <auto-generated> // Generated by the protocol buffer compiler. DO NOT EDIT! // source: sample.proto // </auto-generated> #pragma warning disable 1591, 0612, 3021, 8981 #region Designer generated code using pb = global::Google.Protobuf; using pbc = global::Google.Protobuf.Collections; using pbr = global::Google.Protobuf.Reflection; using scg = global::System.Collections.Generic; namespace Sample.Messages { /// <summary>Holder for reflection information generated from sample.proto</summary> public static partial class SampleReflection { #region Descriptor /// <summary>File descriptor for sample.proto</summary> public static pbr::FileDescriptor Descriptor { get { return descriptor; } } private static pbr::FileDescriptor descriptor; static SampleReflection() { byte[] descriptorData = global::System.Convert.FromBase64String( string.Concat( "CgxzYW1wbGUucHJvdG8iPQoGUGVyc29uEgoKAmlkGAEgASgFEgwKBG5hbWUY", "AiABKAkSGQoHYWRkcmVzcxgDIAEoCzIILkFkZHJlc3MiJwoHQWRkcmVzcxIN", "CgVsaW5lMRgBIAEoCRINCgVsaW5lMhgCIAEoCUISqgIPU2FtcGxlLk1lc3Nh", "Z2VzYgZwcm90bzM=")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Sample.Messages.Person), global::Sample.Messages.Person.Parser, new[]{ "Id", "Name", "Address" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Sample.Messages.Address), global::Sample.Messages.Address.Parser, new[]{ "Line1", "Line2" }, null, null, null, null) })); } #endregion } #region Messages [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")] public sealed partial class Person : pb::IMessage<Person> #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE , pb::IBufferMessage #endif { private static readonly pb::MessageParser<Person> _parser = new pb::MessageParser<Person>(() => new Person()); private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public static pb::MessageParser<Person> Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public static pbr::MessageDescriptor Descriptor { get { return global::Sample.Messages.SampleReflection.Descriptor.MessageTypes[0]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] pbr::MessageDescriptor pb::IMessage.Descriptor { get { return Descriptor; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public Person() { OnConstruction(); } partial void OnConstruction(); [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public Person(Person other) : this() { id_ = other.id_; name_ = other.name_; address_ = other.address_ != null ? other.address_.Clone() : null; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public Person Clone() { return new Person(this); } /// <summary>Field number for the "id" field.</summary> public const int IdFieldNumber = 1; private int id_; /// <summary> /// Protobugスタイルガイドではフィールド名にunderscore_separated_namesを利用することが推奨 /// .NET ツールでは自動で UnderScoreSeparatedNames のように変換してくれますs /// </summary> [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public int Id { get { return id_; } set { id_ = value; } } // 長いので省略... } #endregion } #endregion Designer generated code
シリアライズ・デシリアライズ
シリアライズ・デシリアライズするには、先ほど生成された定義を利用するだけなのでとても簡単です。
using Google.Protobuf; using Sample.Messages; internal class Program { public static void Main() { var person = new Person { Id = 12345, Name = "Hanachiru", Address = new Address { Line1 = "Line 1", Line2 = "Line 2" } }; using (var output = File.Create("Sample.dat")) { // 実際に書き込まれるデータ(バイナリ) : 08 B9 60 12 09 48 61 6E 61 63 68 69 72 75 1A 10 0A 06 4C 69 6E 65 20 31 12 06 4C 69 6E 65 20 32 person.WriteTo(output); } Person newPerson; using (var input = File.OpenRead("Sample.dat")) { newPerson = Person.Parser.ParseFrom(input); // 12345 Console.WriteLine(newPerson.Id); // Hanachiru Console.WriteLine(newPerson.Name); // Line 1 Console.WriteLine(newPerson.Address.Line1); // Line 2 Console.WriteLine(newPerson.Address.Line2); } } }
ちなみにバイナリのデータはちゃんとprotobuf-net
を利用した前回と全く同じになっていますね。(むしろなってくれてないと困りますが)
Google.ProtoBuf
の場合はシリアライズ・デシリアライズの処理もコード生成されているので、.proto
がほぼ必須になっているといってもいいでしょう。