はじめに
Google 製のシリアライズツール「Protocol Buffers」を、今更ながら、C# で使ってみました。
「Protocol Buffers って何?」という人は、次の記事を読むといいです。
C# で ProtocolBuffers を使うには
C# で ProtocolBuffers を使うためのライブラリが、既にたくさん作られています。その中で今回は、「protobuf-net」というライブラリを使ってみました。
protobuf-net の使い方
使い方はすごく簡単。
- プロジェクトの参照設定で protobuf-net を追加
- シリアライズしたいクラスに ProtoContract 属性を付ける
- メンバに ProtoMember 属性を付ける
- Serializer クラスの Serialize メソッドでシリアライズ
- Serializer クラスの Deserialize メソッドでデシリアライズ
サンプルコードがこちら。
using System; using System.IO; using ProtoBuf; namespace ProtoBufSample { // ProtocolBuffers でシリアライズしたいクラス [ProtoContract] public class Item { [ProtoMember(1)] public string Key { get; set; } [ProtoMember(2)] public string Name { get; set; } [ProtoMember(3)] public DateTime CreatedAt { get; set; } [ProtoMember(4)] public bool Done { get; set; } } class Program { static void Main(string[] args) { Item item = new Item { Key = "Foo", Name = "Bar", CreatedAt = DateTime.Now, Done = true, }; using (Stream stream = new MemoryStream()) using (StreamReader reader = new StreamReader(stream)) { // シリアライズ Serializer.Serialize(stream, item); // コンソールに出力する stream.Seek(0, SeekOrigin.Begin); Console.WriteLine(reader.ReadToEnd()); // 元に戻す stream.Seek(0, SeekOrigin.Begin); Item result = Serializer.Deserialize<Item>(stream); Console.WriteLine(item.Key); Console.WriteLine(item.Name); } Console.ReadLine(); } } }
ProtoMember のコンストラクタは tag という引数を受け取ります。これは 1 から始まる整数で、重複不可。メンバを識別する ID みたいなものです。
このサンプルの実行結果がこちら。
クラスの属性を付けるだけでいいという、手軽さが魅力ですね。
.proto ファイルも使えます
ProtoBuf.NET には protogen というツールが付属してます。このツールを使えば、.proto ファイルをもとにクラスのソースコードを生成できます。
package protobufsample; message Item { required string key = 1; required string name = 2; required bool done = 3; required string created_at = 4; }
この .proto ファイルを protogen に渡して、C# 用のクラスを生成します。
protogen.exe -i:protoファイル名 -o:出力ファイル名
protobuf と使い方が違うので注意。先ほどの .proto ファイルを元に生成したクラスがこちら。
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ // Generated from: models.proto namespace protobufsample { [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Item")] public partial class Item : global::ProtoBuf.IExtensible { public Item() {} private string _key; [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"key", DataFormat = global::ProtoBuf.DataFormat.Default)] public string key { get { return _key; } set { _key = value; } } private string _name; [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"name", DataFormat = global::ProtoBuf.DataFormat.Default)] public string name { get { return _name; } set { _name = value; } } private bool _done; [global::ProtoBuf.ProtoMember(3, IsRequired = true, Name=@"done", DataFormat = global::ProtoBuf.DataFormat.Default)] public bool done { get { return _done; } set { _done = value; } } private string _created_at; [global::ProtoBuf.ProtoMember(4, IsRequired = true, Name=@"created_at", DataFormat = global::ProtoBuf.DataFormat.Default)] public string created_at { get { return _created_at; } set { _created_at = value; } } private global::ProtoBuf.IExtension extensionObject; global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } } }
あとは属性のときと同じように、Serializer を使ってシリアライズします。