MessagePack-CSharp

serializationMessagePackC#.NETUnityhigh-performancebinary-formatsource-generator

Serialization Library

MessagePack-CSharp

Overview

MessagePack-CSharp is an extremely fast MessagePack serializer for C#/.NET developed by neuecc. It achieves over 10x faster performance than JSON and significantly reduces data size through binary serialization. Targeting .NET Standard 2.0, it supports a wide range of platforms including Unity, Xamarin, .NET Core, and .NET Framework.

Details

MessagePack-CSharp is a performance-first serializer that employs optimization techniques such as zero-allocation deserialization, dynamic code generation, and buffer pooling. It supports compile-time code generation through Source Generators, enabling high-speed operation even in AOT environments.

Key Features:

  • Ultra-high Performance: 10x faster than MsgPack-Cli, 17x+ faster than JSON.NET
  • Compact Binary Size: 50-70% size reduction compared to JSON
  • Full Unity Support: Built-in support for Unity standard types (Vector3, Quaternion, etc.)
  • Source Generator Support: AOT compatibility and compile-time validation
  • LZ4 Compression Support: Further size reduction through fast compression
  • Union Type Support: Polymorphic serialization capabilities
  • Security Features: Safe deserialization of untrusted data

Architecture:

  • Fastest serialization with indexed keys (IntKey)
  • Readable debugging with string keys (StringKey)
  • Contractless mode for attribute-free serialization
  • Extensibility through custom formatters

Pros and Cons

Pros

  • Outstanding Performance: One of the fastest serializers in the C# ecosystem
  • Memory Efficiency: Zero-allocation design minimizes GC pressure
  • Unity Integration: Proven track record in game development with Unity-specific optimizations
  • Type Safety: Compile-time validation through Source Generators
  • Flexible Configuration: Three serialization modes for different use cases
  • Compression Support: Network transfer optimization with LZ4 fast compression
  • Active Development: Continuous maintenance by Cysharp

Cons

  • Binary Format: Human-unreadable format makes debugging difficult
  • Learning Curve: Understanding required for advanced features
  • Attribute-based: MessagePackObject attributes typically required
  • Versioning: Careful management needed for schema compatibility

Reference Pages

Code Examples

Basic Serialization

// 1. Using indexed keys (fastest)
[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; }
}

// Serialization
var person = new Person { Id = 1, Name = "John", Age = 30 };
byte[] bytes = MessagePackSerializer.Serialize(person);

// Deserialization
Person deserialized = MessagePackSerializer.Deserialize<Person>(bytes);

// Verify in JSON format
string json = MessagePackSerializer.ConvertToJson(bytes);
Console.WriteLine(json); // [1,"John",30]

String Keys and Contractless Mode

// 2. String keys (easier debugging)
[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 mode (no attributes required)
public class Order
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public List<Product> Items { get; set; }
}

// Contractless mode serialization
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 Optimization

// Unity initialization
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void InitializeMessagePack()
{
    // Enable Unity type support
    var options = MessagePackSerializerOptions.Standard
        .WithResolver(MessagePack.Unity.UnityResolver.Instance);
    
    MessagePackSerializer.DefaultOptions = options;
}

// Unity type serialization
[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; }
}

// High-speed struct array serialization (Unsafe)
StaticCompositeResolver.Instance.Register(
    MessagePack.Unity.UnityResolver.Instance,
    MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance,
    MessagePack.Resolvers.StandardResolver.Instance
);

// Optimal for large data like mesh data
Vector3[] vertices = mesh.vertices;
byte[] bytes = MessagePackSerializer.Serialize(vertices);

LZ4 Compression and Security Settings

// Enable LZ4 compression
var lz4Options = MessagePackSerializerOptions.Standard
    .WithCompression(MessagePackCompression.Lz4BlockArray);

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

// Handle untrusted data
var secureOptions = MessagePackSerializerOptions.Standard
    .WithSecurity(MessagePackSecurity.UntrustedData);

try
{
    var result = MessagePackSerializer.Deserialize<MyData>(untrustedData, secureOptions);
}
catch (MessagePackSerializationException ex)
{
    // Detect security violations
    Console.WriteLine($"Security error: {ex.Message}");
}

Source Generator and AOT Support

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

// Enable Source Generator in project file
// <PackageReference Include="MessagePack.Generator" Version="2.5.*" />

// Use generated resolver
var options = MessagePackSerializerOptions.Standard
    .WithResolver(GeneratedResolver.Instance);

// High-speed operation even in AOT environments like Unity IL2CPP
MessagePackSerializer.DefaultOptions = options;

Custom Formatter

// Custom formatter to save DateTime as Unix time
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;
    }
}

// Register custom formatter
var resolver = CompositeResolver.Create(
    new IMessagePackFormatter[] { new UnixTimeDateTimeFormatter() },
    new IFormatterResolver[] { StandardResolver.Instance }
);

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

Union Types (Polymorphism)

[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() => "Meow";
}

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

// Polymorphic serialization
Animal[] animals = new Animal[]
{
    new Cat { Name = "Whiskers" },
    new Dog { Name = "Buddy", Breed = "Golden Retriever" }
};

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