Protocol Buffers のC#版
有名どころでは、以下の2つがあるようです。
- protobuf-net
- 特徴:.protoファイルが不要で、すごくシンプルにシリアライズ/デシリアライズできる。
 
- 特徴:
- Google.Protobuf
- 特徴:.protoファイルが必要で、事前にシリアライズ/デシリアライズ用のコードを生成する必要があるが、.protoファイルを使用する他サービスとの連携に適している。
 
- 特徴:
※ 追記:前者でも .proto ファイルを使用することができるみたいです。
今回の私が手掛ける案件では、後者のほうが適する(CのサービスとC#のサービスがやりとりする)ので、ここでは Google.Protobuf を主に取り上げます。
参考サイト
C# 版 protobuf (Google.Protobuf) の導入
githubのReadmeに書かれている手順で導入していきます。
条件
以下が条件です。
- Visual Studio 2012 意向であること
- .NET4.5 以降または .NET Core であること
Nuget
Google.Protobuf を使用するだけなら、 Google.Protobuf を Nuget すればよいです。
しかし、それに加えて .proto ファイルを使用してクラスファイルを生成するならば、 Google.Protobuf.Tools も Nuget する必要があります。
Google.Protobuf はライブラリなのだが、 Google.Protobuf.Tools はライブラリではなく、バイナリ(ptoroc.exe)が入っています。
ちなみに、 Nuget した際のダウンロード先は、私の場合はC:\Users\XXX\.nuget\packages\google.protobuf.tools\3.5.1\tools でした。
ソリューションファイル内にダウンロードされているものだと思っていたので、ちょっとはまってしまいました。
Google.Protobuf でチュートリアルする
とりあえず、公式のチュートリアルを実行してみます。
公式は英語なので、備忘録として意訳したやつを残しておきます。
.proto ファイルの用意
とりあえずチュートリアルで示されている addressbook.proto を使用します。これはgithubに掲載されているのだが、このまま使用するとエラーになる(import "google/protobuf/timestamp.proto" なんてねーよって言われる)ので、修正したのを↓に載せときます。
// [START declaration]
syntax = "proto3";
package tutorial;
// [END declaration]
// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]
// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]
// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }
  repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages].proto ファイルからクラスファイルを生成する
Nuget した Google.Protobuf.Tools 内に含まれる tools\windows_x64\protoc.exe を使用して、addressbook.proto のシリアライズ/デシリアライズ用のクラスファイルを生成しあmす。
protoc -I=$SRC_DIR --csharp_out=$DST_DIR $SRC_DIR/addressbook.proto
上記を実行すると、Addressbook.cs が $DST_DIR 内に生成されます。
(Addressbook.cs の中身を見ると、なかなかキモくて焦るが、たぶん利用する側は中身を意識する必要はそんなになさそう)
生成されたクラスファイルと Google.Protobuf を使用して、シリアライズ/デシリアライズをやってみる
チュートリアルを参考に、以下のように動作確認用の Program.cs を作成して実行してみます。
実行結果、正しく動作していることが確認できました。
using System;
using System.IO;
using System.Text;
using Google.Protobuf;
using Google.Protobuf.Examples.AddressBook; // protoc.exeにより自動生成されたクラスの名前空間
using static Google.Protobuf.Examples.AddressBook.Person.Types; // C# 6 の書き方で、クラス内クラスを省略形式で記述することができるようになる(protobufとは関係なし)
namespace ProtobufCsharp
{
    class Program
    {
        static void Main(string[] args)
        {
            // AddressBook.csで定義されているPresonクラスを実体化する
            Person person = new Person
            {
                Id = 1234,
                Name = "Yamada Tarou",
                Email = "yamada@sample.com",
                Phones = { new PhoneNumber { Number = "555-4321", Type = PhoneType.Home },
                    new PhoneNumber { Number = "222-1111", Type = PhoneType.Work } }
            };
            // 文字列にシリアライズ
            var data = Serialize(person);
            // シリアライズした文字列を読み込んでデシリアライズする
            var someone = Deserialize<Person>(data);
            // 動作確認
            Console.WriteLine(\$"Id:{someone.Id}, Name:{someone.Name}, Email:{someone.Email}, " +
                $"Phones[0](Number:{someone.Phones[0].Number}, Type:{someone.Phones[0].Type}), " +
                $"Phones[1](Number:{someone.Phones[1].Number}, Type:{someone.Phones[1].Type})");
            Console.ReadKey();
        }
        static byte[] Serialize<T>(T obj) where T : IMessage<T>
        {
            using (var stream = new MemoryStream())
            {
                obj.WriteTo(stream);
                return stream.ToArray();
            }
        }
        static T Deserialize<T>(byte[] data) where T : IMessage<T>, new()
        {
            var parser = new MessageParser<T>(() => new T());
            return parser.ParseFrom(new MemoryStream(data));
        }
    }
}解説
- WriteTo(stream)メソッドで、- Stream型にシリアライズすることができる
- Parser.ParseFrom(stream)メソッドで、- Stream型からデシリアライズすることができる
- 実際、ネットワーク間でメッセージのやり取りをする場合は、文字列が都合がいい場合が多いので、その場合は MemoryStream型を byte 配列に変換すればよい
とりあえず思ったより簡単に実行できました。
ただし、Stream 型を使用するシリアライズ/デシリアライズはちょっと面倒だ。直に byte 配列にできればいいのに。
ちょっと調査します。
直に byte 配列にシリアライズ/デシリアライズするようにサンプルを変更してみました。たぶんこれでいける。
 
  
  
  
  
コメント