MessagePack-CSharp

シリアライゼーションMessagePackC#.NETUnity高速バイナリフォーマットソースジェネレーター

シリアライゼーションライブラリ

MessagePack-CSharp

概要

MessagePack-CSharpは、neueccによって開発されたC#/.NET向けの超高速MessagePackシリアライザーです。JSONの10倍以上の高速化を実現し、データサイズも大幅に削減できるバイナリシリアライゼーションライブラリです。.NET Standard 2.0準拠で、Unity、Xamarin、.NET Core、.NET Frameworkなど幅広いプラットフォームに対応しています。

詳細

MessagePack-CSharpは、パフォーマンスを最優先に設計されたシリアライザーで、ゼロアロケーション逆シリアル化、動的コード生成、バッファプーリングなどの最適化技術を採用しています。Source Generatorによるコンパイル時コード生成に対応し、AOT環境でも高速動作を実現します。

主な特徴:

  • 超高速パフォーマンス: MsgPack-Cliの10倍、JSON.NETの17倍以上高速
  • コンパクトなバイナリサイズ: JSONと比較して50-70%のサイズ削減
  • Unity完全対応: Unity標準型(Vector3、Quaternionなど)の組み込みサポート
  • Source Generator対応: AOT環境での動作とコンパイル時検証
  • LZ4圧縮サポート: 高速圧縮による更なるサイズ削減
  • Union型サポート: ポリモーフィックなシリアライゼーション
  • セキュリティ機能: 信頼できないデータの安全な逆シリアル化

アーキテクチャ:

  • インデックスキー(IntKey)による最速シリアライゼーション
  • 文字列キー(StringKey)による可読性の高いデバッグ
  • Contractlessモードによる属性不要のシリアライゼーション
  • カスタムフォーマッターによる拡張性

メリット・デメリット

メリット

  • 圧倒的なパフォーマンス: C#界隈で最速クラスのシリアライザー
  • メモリ効率: ゼロアロケーション設計により GC圧力を最小化
  • Unity統合: ゲーム開発での実績多数、Unity専用最適化
  • 型安全性: Source Generatorによるコンパイル時検証
  • 柔軟な設定: 用途に応じた3つのシリアライゼーションモード
  • 圧縮対応: LZ4による高速圧縮でネットワーク転送最適化
  • 活発な開発: Cysharpによる継続的なメンテナンス

デメリット

  • バイナリフォーマット: 人間が読めない形式のため、デバッグが困難
  • 学習曲線: 高度な機能を使いこなすには理解が必要
  • 属性ベース: 基本的にはMessagePackObject属性の付与が必要
  • バージョニング: スキーマ変更時の互換性管理に注意が必要

参考ページ

書き方の例

基本的なシリアライゼーション

// 1. インデックスキーを使用(最速)
[MessagePackObject]
public class Person
{
    [Key(0)]
    public int Id { get; set; }
    
    [Key(1)]
    public string Name { get; set; }
    
    [Key(2)]
    public int Age { get; set; }
    
    [IgnoreMember]
    public string IgnoredProperty { get; set; }
}

// シリアライゼーション
var person = new Person { Id = 1, Name = "田中", Age = 30 };
byte[] bytes = MessagePackSerializer.Serialize(person);

// 逆シリアライゼーション
Person deserialized = MessagePackSerializer.Deserialize<Person>(bytes);

// JSON形式で確認
string json = MessagePackSerializer.ConvertToJson(bytes);
Console.WriteLine(json); // [1,"田中",30]

文字列キーとContractlessモード

// 2. 文字列キー(デバッグしやすい)
[MessagePackObject]
public class Product
{
    [Key("product_id")]
    public int ProductId { get; set; }
    
    [Key("name")]
    public string Name { get; set; }
    
    [Key("price")]
    public decimal Price { get; set; }
}

// 3. Contractlessモード(属性不要)
public class Order
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public List<Product> Items { get; set; }
}

// Contractlessモードでのシリアライゼーション
var options = MessagePack.Resolvers.ContractlessStandardResolver.Options;
var order = new Order
{
    OrderId = 1001,
    OrderDate = DateTime.Now,
    Items = new List<Product> { /* ... */ }
};

byte[] bytes = MessagePackSerializer.Serialize(order, options);
Order deserialized = MessagePackSerializer.Deserialize<Order>(bytes, options);

Unity向け最適化

// Unity初期化
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void InitializeMessagePack()
{
    // Unity型のサポートを有効化
    var options = MessagePackSerializerOptions.Standard
        .WithResolver(MessagePack.Unity.UnityResolver.Instance);
    
    MessagePackSerializer.DefaultOptions = options;
}

// Unity型のシリアライゼーション
[MessagePackObject]
public class GameData
{
    [Key(0)]
    public Vector3 Position { get; set; }
    
    [Key(1)]
    public Quaternion Rotation { get; set; }
    
    [Key(2)]
    public Color Color { get; set; }
}

// 高速な構造体配列のシリアライゼーション(Unsafe)
StaticCompositeResolver.Instance.Register(
    MessagePack.Unity.UnityResolver.Instance,
    MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance,
    MessagePack.Resolvers.StandardResolver.Instance
);

// メッシュデータなどの大量データに最適
Vector3[] vertices = mesh.vertices;
byte[] bytes = MessagePackSerializer.Serialize(vertices);

LZ4圧縮とセキュリティ設定

// LZ4圧縮を有効化
var lz4Options = MessagePackSerializerOptions.Standard
    .WithCompression(MessagePackCompression.Lz4BlockArray);

byte[] compressed = MessagePackSerializer.Serialize(data, lz4Options);
var decompressed = MessagePackSerializer.Deserialize<MyData>(compressed, lz4Options);

// 信頼できないデータの処理
var secureOptions = MessagePackSerializerOptions.Standard
    .WithSecurity(MessagePackSecurity.UntrustedData);

try
{
    var result = MessagePackSerializer.Deserialize<MyData>(untrustedData, secureOptions);
}
catch (MessagePackSerializationException ex)
{
    // セキュリティ違反を検出
    Console.WriteLine($"セキュリティエラー: {ex.Message}");
}

Source GeneratorとAOT対応

// Source Generator用の属性
[GeneratedMessagePackResolver]
[MessagePackObject]
public partial class MyModel
{
    [Key(0)]
    public int Id { get; set; }
    
    [Key(1)]
    public string Name { get; set; }
}

// プロジェクトファイルでSource Generatorを有効化
// <PackageReference Include="MessagePack.Generator" Version="2.5.*" />

// 生成されたリゾルバーの使用
var options = MessagePackSerializerOptions.Standard
    .WithResolver(GeneratedResolver.Instance);

// Unity IL2CPPなどAOT環境でも高速動作
MessagePackSerializer.DefaultOptions = options;

カスタムフォーマッター

// DateTimeを Unix時間で保存するカスタムフォーマッター
public class UnixTimeDateTimeFormatter : IMessagePackFormatter<DateTime>
{
    public void Serialize(ref MessagePackWriter writer, DateTime value, 
        MessagePackSerializerOptions options)
    {
        var unixTime = ((DateTimeOffset)value).ToUnixTimeSeconds();
        writer.Write(unixTime);
    }

    public DateTime Deserialize(ref MessagePackReader reader, 
        MessagePackSerializerOptions options)
    {
        var unixTime = reader.ReadInt64();
        return DateTimeOffset.FromUnixTimeSeconds(unixTime).DateTime;
    }
}

// カスタムフォーマッターの登録
var resolver = CompositeResolver.Create(
    new IMessagePackFormatter[] { new UnixTimeDateTimeFormatter() },
    new IFormatterResolver[] { StandardResolver.Instance }
);

var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);

Union型(ポリモーフィズム)

[Union(0, typeof(Cat))]
[Union(1, typeof(Dog))]
public abstract class Animal
{
    public abstract string MakeSound();
}

[MessagePackObject]
public class Cat : Animal
{
    [Key(0)]
    public string Name { get; set; }
    
    public override string MakeSound() => "ニャー";
}

[MessagePackObject]
public class Dog : Animal
{
    [Key(0)]
    public string Name { get; set; }
    
    [Key(1)]
    public string Breed { get; set; }
    
    public override string MakeSound() => "ワン";
}

// ポリモーフィックなシリアライゼーション
Animal[] animals = new Animal[]
{
    new Cat { Name = "ミケ" },
    new Dog { Name = "ポチ", Breed = "柴犬" }
};

byte[] bytes = MessagePackSerializer.Serialize(animals);
Animal[] deserialized = MessagePackSerializer.Deserialize<Animal[]>(bytes);