protobuf-net
シリアライゼーションライブラリ
protobuf-net
概要
protobuf-netは、Marc Gravellによって開発された.NET向けのProtocol Buffers実装です。Googleが開発したProtocol Buffersのバイナリフォーマットを使用しながら、.NETの慣習に従った使いやすいAPIを提供します。属性ベースのコードファーストアプローチと、.protoファイルを使用したコントラクトファーストアプローチの両方をサポートし、高性能なシリアライゼーションを実現します。
詳細
protobuf-netは、.NET環境に最適化されたProtocol Buffers実装で、以下の特徴を持ちます:
主要な特徴
-
.NET慣習に従った設計
- XmlSerializerやDataContractSerializerと同様の使用感
- 属性ベースの簡単な設定
- .NET開発者にとって直感的なAPI
-
高速・高効率
- ランタイムコード生成による最適化
- 最小限のメモリ使用
- BinaryFormatterと比較して約20倍高速
-
柔軟な開発アプローチ
- コードファースト:C#クラスから開始
- コントラクトファースト:.protoファイルから開始
- 両アプローチ間の相互変換も可能
-
豊富な.NET機能サポート
- 継承のサポート(protobuf-net独自拡張)
- Nullable型の完全サポート
- コレクションとジェネリクスのサポート
- シリアライゼーションコールバック
-
ビルドツール統合
- MSBuildとの完全統合
- .protoファイルからの自動コード生成
- NuGetパッケージによる簡単な導入
パフォーマンス特性
- シリアライゼーション速度:BinaryFormatterの約8-10倍高速
- デシリアライゼーション速度:BinaryFormatterの約11-12倍高速
- データサイズ:XMLの約1/10、JSONの約1/3-1/5
- メモリ効率:ストリーミングAPIによる低メモリ使用
メリット・デメリット
メリット
- 高性能:.NET環境で最速レベルのシリアライゼーション
- 小さいデータサイズ:効率的なバイナリフォーマット
- 使いやすさ:.NET開発者にとって自然な属性ベースのAPI
- 柔軟性:コードファーストとコントラクトファーストの両対応
- 互換性:標準的なProtocol Buffersフォーマットを使用
- ストリーミング対応:大容量データの効率的な処理
- 継承サポート:.NET特有の継承関係をサポート
デメリット
- 人間可読性なし:バイナリフォーマットのため直接読めない
- スキーマ進化の制限:フィールド番号の管理が必要
- 学習コスト:Protocol Buffersの概念理解が必要
- デバッグの難しさ:バイナリデータのデバッグは困難
- クロスプラットフォーム制約:コードファーストは.NET限定
参考ページ
書き方の例
基本的な使用方法(属性ベース)
using ProtoBuf;
using System;
using System.IO;
// データモデルの定義
[ProtoContract]
public class Person
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public DateTime BirthDate { get; set; }
[ProtoMember(4)]
public Address Address { get; set; }
}
[ProtoContract]
public class Address
{
[ProtoMember(1)]
public string Street { get; set; }
[ProtoMember(2)]
public string City { get; set; }
[ProtoMember(3)]
public string PostalCode { get; set; }
}
// シリアライゼーションとデシリアライゼーション
class Program
{
static void Main()
{
var person = new Person
{
Id = 1,
Name = "田中太郎",
BirthDate = new DateTime(1990, 1, 1),
Address = new Address
{
Street = "東京都千代田区1-1",
City = "東京",
PostalCode = "100-0001"
}
};
// シリアライゼーション
using (var file = File.Create("person.bin"))
{
Serializer.Serialize(file, person);
}
// デシリアライゼーション
Person loadedPerson;
using (var file = File.OpenRead("person.bin"))
{
loadedPerson = Serializer.Deserialize<Person>(file);
}
Console.WriteLine($"Name: {loadedPerson.Name}");
}
}
コントラクトファーストアプローチ
// person.proto
syntax = "proto3";
package example;
message Person {
int32 id = 1;
string name = 2;
string birth_date = 3;
Address address = 4;
}
message Address {
string street = 1;
string city = 2;
string postal_code = 3;
}
<!-- .csprojファイルに追加 -->
<ItemGroup>
<PackageReference Include="protobuf-net.BuildTools" Version="3.*"
PrivateAssets="all" />
<AdditionalFiles Include="person.proto" />
</ItemGroup>
継承のサポート
[ProtoContract]
[ProtoInclude(7, typeof(Employee))]
[ProtoInclude(8, typeof(Customer))]
public abstract class Person
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
}
[ProtoContract]
public class Employee : Person
{
[ProtoMember(1)]
public string Department { get; set; }
[ProtoMember(2)]
public decimal Salary { get; set; }
}
[ProtoContract]
public class Customer : Person
{
[ProtoMember(1)]
public string CustomerNumber { get; set; }
[ProtoMember(2)]
public DateTime RegistrationDate { get; set; }
}
コレクションとジェネリクス
[ProtoContract]
public class OrderList
{
[ProtoMember(1)]
public List<Order> Orders { get; set; } = new List<Order>();
[ProtoMember(2)]
public Dictionary<int, Customer> CustomerMap { get; set; }
= new Dictionary<int, Customer>();
[ProtoMember(3, OverwriteList = true)]
public HashSet<string> Tags { get; set; } = new HashSet<string>();
}
[ProtoContract]
public class Order
{
[ProtoMember(1)]
public int OrderId { get; set; }
[ProtoMember(2)]
public decimal Amount { get; set; }
[ProtoMember(3)]
public DateTime OrderDate { get; set; }
}
// 使用例
var orderList = new OrderList();
orderList.Orders.Add(new Order
{
OrderId = 1,
Amount = 1000.0m,
OrderDate = DateTime.Now
});
// メモリストリームへのシリアライゼーション
using var ms = new MemoryStream();
Serializer.Serialize(ms, orderList);
var bytes = ms.ToArray();
パフォーマンスチューニング
// RuntimeTypeModelを使用した高度な設定
var model = RuntimeTypeModel.Default;
// 型の事前コンパイル
model.CompileInPlace();
// または完全なコンパイル済みモデルの生成
var compiledModel = model.Compile();
// カスタムシリアライゼーション設定
model[typeof(Person)]
.Add(1, "Id")
.Add(2, "Name")
.Add(3, "BirthDate");
// ストリーミングAPIの使用
public static async Task SerializeLargeDataAsync<T>(
Stream stream, IEnumerable<T> items)
{
foreach (var item in items)
{
Serializer.SerializeWithLengthPrefix(stream, item,
PrefixStyle.Base128);
await stream.FlushAsync();
}
}
// 読み込み時のストリーミング
public static async IAsyncEnumerable<T> DeserializeLargeDataAsync<T>(
Stream stream)
{
while (stream.Position < stream.Length)
{
var item = Serializer.DeserializeWithLengthPrefix<T>(
stream, PrefixStyle.Base128);
if (item == null) yield break;
yield return item;
}
}
Null値の処理
[ProtoContract]
public class NullableExample
{
// 通常のnull許容型
[ProtoMember(1)]
public int? OptionalInt { get; set; }
// Null値をラップして送信
[ProtoMember(2), NullWrappedValue]
public int? WrappedInt { get; set; }
// Nullコレクションの処理
[ProtoMember(3), NullWrappedCollection]
public List<string> OptionalList { get; set; }
// 条件付きシリアライゼーション
[ProtoMember(4)]
public string ConditionalField { get; set; }
public bool ShouldSerializeConditionalField()
{
return !string.IsNullOrEmpty(ConditionalField);
}
}
これらの例は、protobuf-netの主要な機能を示しています。高性能なバイナリシリアライゼーションが必要な.NETアプリケーションに最適な選択肢です。