| name | migrant |
| description | Guidance for Migrant fast binary serialization library for .NET. USE FOR: fast binary serialization of complex object graphs, version-tolerant deserialization, simulation state snapshots, game save/load systems, deep object cloning via serialization, internal data persistence with circular reference support. DO NOT USE FOR: cross-language interop (use protobuf-net), REST API responses (use System.Text.Json), human-readable data formats, or long-term archival storage with strict schema guarantees.
|
| license | MIT |
| metadata | {"displayName":"Migrant","author":"Tyler-R-Kendrick","version":"1.0.0"} |
| compatibility | ["claude","copilot","cursor"] |
| references | [{"title":"Migrant GitHub Repository","url":"https://github.com/antmicro/Migrant"},{"title":"Migrant Project Page","url":"https://opensource.antmicro.com/projects/Migrant/"}] |
Migrant
Overview
Migrant is a fast, flexible binary serialization framework for .NET that emphasizes ease of use and version-tolerant deserialization. It can serialize virtually any .NET object graph, including private fields, circular references, and types implementing ISerializable, without requiring explicit attributes or schema definitions. Migrant generates optimized serialization code at runtime using IL emission, delivering performance close to hand-written serialization. It is particularly well-suited for simulation state snapshots, game save systems, and any scenario where you need to persist complex in-memory object graphs with minimal configuration.
Basic Serialization
Serialize and deserialize objects with zero configuration.
using Antmicro.Migrant;
using System.IO;
public class GameState
{
public string PlayerName { get; set; } = string.Empty;
public int Level { get; set; }
public double Health { get; set; }
public List<string> Inventory { get; set; } = new();
public Dictionary<string, int> Skills { get; set; } = new();
}
var serializer = new Serializer();
var state = new GameState
{
PlayerName = "Alice",
Level = 42,
Health = 87.5,
Inventory = { "Sword", "Shield", "Potion" },
Skills = { ["Combat"] = 10, ["Magic"] = 7 }
};
using (var fileStream = File.Create("savegame.bin"))
{
serializer.Serialize(state, fileStream);
}
using (var fileStream = File.OpenRead("savegame.bin"))
{
loaded = serializer.Deserialize<GameState>(fileStream);
}
Serializer Settings
Configure Migrant behavior with the Settings class.
using Antmicro.Migrant;
using Antmicro.Migrant.Customization;
var settings = new Settings(
supportForISerializable: true,
useBuffering: true,
disableTypeStamping: false,
referencePreservation: ReferencePreservation.Preserve,
versionTolerance: VersionToleranceLevel.AllowFieldAddition
| VersionToleranceLevel.AllowFieldRemoval
);
var serializer = new Serializer(settings);
Version-Tolerant Deserialization
Migrant handles type evolution gracefully when fields are added or removed.
using Antmicro.Migrant;
using Antmicro.Migrant.Customization;
using System.IO;
public class ConfigV1
{
public string Name { get; set; } = string.Empty;
public int Timeout { get; set; }
}
public class ConfigV2
{
public string Name { get; set; } = string.Empty;
public int Timeout { get; set; }
public string Region { get; set; } = "us-east-1";
public bool Enabled { get; set; } = true;
}
var settings = new Settings(
versionTolerance: VersionToleranceLevel.AllowFieldAddition
| VersionToleranceLevel.AllowFieldRemoval
| VersionToleranceLevel.AllowGuidChange);
var serializer = new Serializer(settings);
using var stream = MemoryStream();
serializer.Serialize( ConfigV1 { Name = , Timeout = }, stream);
stream.Position = ;
config = serializer.Deserialize<ConfigV2>(stream);
Circular Reference and Deep Graph Handling
Migrant handles circular references and deep object graphs automatically.
using Antmicro.Migrant;
using System.IO;
public class SimulationNode
{
public string Id { get; set; } = string.Empty;
public double Value { get; set; }
public SimulationNode? Next { get; set; }
public SimulationNode? Previous { get; set; }
public List<SimulationNode> Connections { get; set; } = new();
}
var serializer = new Serializer();
var node1 = new SimulationNode { Id = "A", Value = 1.0 };
var node2 = new SimulationNode { Id = "B", Value = 2.0 };
var node3 = new SimulationNode { Id = "C", Value = 3.0 };
node1.Next = node2; node2.Previous = node1;
node2.Next = node3; node3.Previous = node2;
node3.Next = node1; node1.Previous = node3;
node1.Connections.AddRange(new[] { node2, node3 });
node2.Connections.AddRange(new[] { node1, node3 });
using var stream = new MemoryStream();
serializer.Serialize(node1, stream);
stream.Position = ;
restored = serializer.Deserialize<SimulationNode>(stream);
Deep Cloning via Serialization
Use Migrant's serializer for deep cloning complex object graphs.
using Antmicro.Migrant;
using System.IO;
public static class DeepCloner
{
private static readonly Serializer _serializer = new(new Settings(
useBuffering: true,
referencePreservation: ReferencePreservation.Preserve));
public static T Clone<T>(T obj)
{
using var stream = new MemoryStream();
_serializer.Serialize(obj, stream);
stream.Position = 0;
return _serializer.Deserialize<T>(stream);
}
}
var original = new GameState
{
PlayerName = "Bob",
Level = 10,
Inventory = { "Axe", "Torch" }
};
var snapshot = DeepCloner.Clone(original);
snapshot.Inventory.Add("Gem");
Transient and Custom Serialization Hooks
Control serialization behavior with attributes and hooks.
using Antmicro.Migrant;
using System.IO;
public class CachedService
{
public string ServiceName { get; set; } = string.Empty;
public string ConnectionString { get; set; } = string.Empty;
[Transient]
private HttpClient? _httpClient;
[PostDeserialization]
private void OnDeserialized()
{
_httpClient = new HttpClient
{
BaseAddress = new Uri(ConnectionString)
};
}
public HttpClient GetClient() =>
_httpClient ?? throw new InvalidOperationException(
"Service not initialized.");
}
var serializer = new Serializer();
var service = new CachedService
{
ServiceName = "OrderAPI",
ConnectionString = "https://api.example.com"
};
using var stream = new MemoryStream();
serializer.Serialize(service, stream);
stream.Position = 0;
var restored = serializer.Deserialize<CachedService>(stream);
Serializer Comparison
| Feature | Migrant | Hyperion | BinaryFormatter | System.Text.Json |
|---|
| Format | Binary | Binary | Binary | JSON (text) |
| Schema required | No | No | No | No |
| Circular references | Yes | Yes | Yes | No |
| Version tolerance | Configurable | Yes | Limited | Limited |
| Performance | Very fast (IL emit) | Fast | Slow | Fast (text) |
| Private fields | Yes | Yes | Yes | No (default) |
| Best for | State snapshots | Akka.NET | Legacy (obsolete) | REST APIs |
Best Practices
- Enable version tolerance flags for any data that persists beyond a single app version: use
AllowFieldAddition | AllowFieldRemoval to ensure saved data remains loadable after class changes.
- Use
[Transient] for runtime-only state: mark fields like HTTP clients, caches, and database connections as transient so they are skipped during serialization and rebuilt on deserialization.
- Implement
[PostDeserialization] hooks to rebuild transient state: use this attribute on private methods to reinitialize connections, caches, or computed values after deserialization.
- Enable
useBuffering for large serialization operations: buffering improves throughput by reducing the number of I/O operations to the underlying stream.
- Use
ReferencePreservation.Preserve when object identity matters: this ensures that two references to the same object remain the same reference after deserialization, critical for circular graphs.
- Create a reusable
Serializer instance: the Migrant serializer generates IL at runtime for each type it encounters; reusing the instance amortizes this startup cost across multiple operations.
- Test version tolerance with actual old serialized data: keep binary snapshots from previous versions in your test suite and verify they deserialize correctly with the current type definitions.
- Avoid using Migrant for cross-process communication: Migrant embeds .NET type information that tightly couples serializer and deserializer; use protobuf-net or Bond for inter-service messaging.
- Use deep cloning sparingly in hot paths: serialization-based cloning is convenient but allocates intermediate buffers; for performance-critical cloning, consider manual copy constructors.
- Monitor serialized data size growth: as types evolve and fields accumulate, serialized payload sizes can grow; periodically benchmark payload sizes and consider migration strategies for bloated types.