The fastest binary serializer for C#.
General-purpose, source-generated, zero-allocation and thread-safe: for services, RPC,
messaging, caching and storage on .NET, with Unity, Godot and HybridCLR support.
🌐 Website · 🚀 Getting Started · What's new in v4 · Benchmarks · vs MemoryPack · 中文
Up to 5.6× faster serialization and up to 11.5× faster in-place deserialization than MemoryPack
Zero GC allocations on hot paths · 221× faster builds than v3
BenchmarkDotNet 0.15.8, .NET 10.0.13, server GC, AMD EPYC 7763 2.59 GHz; Nino v4 and MemoryPack 1.21.4 in one run, Nino 3.3.13 from NuGet directly before it on the same machine. Payloads are about the same size (SimpleClass: 1,104 bytes in v4, 1,152 in v3, 1,148 in MemoryPack). Every case and the method: docs/v4-benchmarks.md; the notes of each release carry that release's numbers.
- Faster than MemoryPack. Serializing classes and polymorphic types is 3.4–5.6× faster. Deserializing them is 1.5–1.9× faster, and raw struct arrays are at least on par (a block copy in both).
- Zero allocation. Serializing into a buffer (
IBufferWriter<byte>) allocates nothing. Deserializing into existing or pooled objects ([NinoPooled], reuse deserialization into existing objects) allocates nothing either (a string whose text is unchanged is kept), and is up to 11.5× faster than MemoryPack's overwrite mode. - Everything happens at compile time. A source generator writes each formatter. There is no reflection, no IL emit, no registration and no init call, so it works on IL2CPP, NativeAOT and HybridCLR hot updates; Nino.Core is annotated for trimming and Native AOT. With the .NET 8 SDK or later, calls are intercepted to the exact formatter, with no virtual call.
- Fast to build. The generator is incremental: one file per type, and only the type you edit is re-generated. Compared with v3, the generated code is 14× smaller and compiles 51× (330 types) to 221× (1,100 types) faster.
- Thread-safe. Serialize and deserialize from any number of threads, with no locks on the hot path.
- Data that evolves. Version tolerance, keyed members, and reference tracking for shared references and cycles. All are opt-in per type, so types that don't use them pay nothing.
- One format everywhere.
netstandard2.0,netstandard2.1,net8.0andnet10.0write identical bytes on CoreCLR, Mono and IL2CPP, 64-bit and 32-bit, x64 and Arm64, so a Unity client and a .NET server talk to each other directly. - The types you already use. Primitives, strings, tuples, every BCL collection,
BigInteger,Uri,Version,Memory<T>,ILookup<,>,Lazy<T>and more, every immutable collection (Nino.Immutable), Unity'sAnimationCurve,GradientandRectOffset, your own collection types, and anything else through a pluggable formatter. Supported types. - ASP.NET Core and TypeScript. Nino.AspNetCore adds MVC
formatters and Minimal API bodies and results for
application/x-nino; Nino.TypeScript generates TypeScript codecs for your types after each build, so browsers and Node read and write the same bytes as .NET. - Mistakes caught while you type. 26 analyzer diagnostics (NINO001–NINO026), many with a one-click fix.
| ⚙️ Services & IPC | Compact binary payloads, no per-call allocations, and safe concurrent use from ASP.NET Core, workers and actors. Nino.AspNetCore reads and writes application/x-nino bodies in MVC and Minimal APIs. |
| 🧩 Web & Node clients | Nino.TypeScript generates TypeScript classes and codecs for your [NinoType]s after each build, so browsers and Node read and write the same bytes as .NET. |
| 🌐 RPC & networking | Serialize straight into an IBufferWriter<byte> (a PipeWriter, a pooled buffer), deserialize from a ReadOnlySpan<byte>, and reuse pooled message objects. Polymorphic messages need no tags. |
| 📨 Message queues & event streams | Polymorphic event types behind one base type, small payloads, and formats that let producers and consumers upgrade at different times. |
| 🗄️ Caching | Values for Redis, IDistributedCache/HybridCache or in-process caches, with schema changes across deployments. |
| 💾 Persistence & storage | Version tolerance and keyed members let you add, reorder and remove fields across releases. Reference tracking keeps object graphs with shared references and cycles. |
| 🎮 Unity | Unity 2022.3+, Mono and IL2CPP, HybridCLR hot-update assemblies. Vector3, Quaternion, Color32 and other unmanaged structs are copied as raw memory; AnimationCurve, Gradient and RectOffset are built in. Install from OpenUPM. |
| 👾 Godot 4 (.NET) | A regular NuGet package for Godot's .NET projects. Godot's math structs (Vector3, Transform3D, …) are unmanaged and copied as raw memory. |
New to Nino? Follow Getting Started on the website: installation for .NET, Unity and Godot, your first type, and the basics.
dotnet add package Nino --prerelease # .NET, Godot
dotnet add package Nino.Immutable --prerelease # optional: System.Collections.Immutable types
dotnet add package Nino.AspNetCore --prerelease # optional: ASP.NET Core MVC formatters and Minimal API bodies
dotnet add package Nino.TypeScript --prerelease # optional: TypeScript codecs for browsers and Node, after each build
openupm add com.jasonxudeveloper.nino # Unity (generator and Unity class formatters included)Nino brings in the runtime (Nino.Serialization) and the source generator (Nino.Generator). Nino v4 is a
prerelease, so --prerelease is required: without it NuGet installs 3.9.x, which cannot read or write v4 data.
using Nino.Core;
[NinoType]
public class Order
{
public long Id;
public string Customer;
public List<OrderLine> Lines;
}
byte[] bytes = NinoSerializer.Serialize(order);
Order copy = NinoDeserializer.Deserialize<Order>(bytes);[NinoType] public abstract class Message { public int Seq; }
[NinoType] public sealed class Ping : Message { public long SentAt; }
[NinoType] public sealed class Chat : Message { public string Text; }
byte[] bytes = NinoSerializer.Serialize<Message>(new Chat { Text = "hi" }); // declared as the base: a type id is written
Message msg = NinoDeserializer.Deserialize<Message>(bytes); // a Chat again: the type id picks the subclassSerialize and deserialize through the common [NinoType] base (not object): generated code dispatches on the type id,
and reuse deserialization and pools work with it. SerializeBoxed/DeserializeBoxed exist only for a type known
only as a Type at run time that shares no [NinoType] base with the others.
// Serialize into the socket pipe: one copy from a pooled per-thread buffer, no byte[] allocated.
NinoSerializer.Serialize(msg, pipeWriter);
// Reuse pooled instances (and their lists) instead of allocating new ones.
[NinoType, NinoPooled]
public sealed partial class ReadingBatch
{
public long Sequence;
public List<SensorReading> Readings;
private static partial IEnumerable<ReadingBatch> OnNinoPoolWarmup() => Enumerable.Range(0, 16).Select(_ => new ReadingBatch());
private partial void OnNinoPoolReturn() { }
}
var batch = NinoDeserializer.Deserialize<ReadingBatch>(span); // taken from the pool
// ... process it ...
batch.ReturnToPool();[NinoType(format: NinoFormat.Keyed)] // members are matched by name: add, reorder, remove or rename them
public class Profile { [NinoFormerName("Mail")] public string Email; public string Name; }
[NinoType(trackReferences: true)] // shared references and cycles survive a round trip
public class Node { public string Name; public List<Node> Links; }Read the documentation on nino.xgamedev.net →, including advanced guides for high-performance serialization, networking, ASP.NET Core, TypeScript clients, message queues, caching, persistence and Unity.
| Nino v4 | MemoryPack 1.21.4 | |
|---|---|---|
| Serialize classes and polymorphic types | 3.4–5.6× faster | |
| Deserialize classes and polymorphic types | 1.5–1.9× faster | |
| Raw struct arrays | at least on par (a block copy in both); v4 zeroes padding, so declare hot structs without it | copies padding as is |
| Deserialize into existing objects | 0 B, up to 11.5× faster (in place) | overwrite mode, allocates |
| Polymorphism | automatic type ids | [MemoryPackUnion] tags you maintain |
| Keyed members, reference tracking, object pools | ✅ | version tolerance and circular references, no pools |
| Unity | 2022.3+, netstandard2.0 and 2.1, HybridCLR |
netstandard2.1 |
MemoryPack still has System.Type (left out of Nino on purpose: data that names the types to create is an attack
vector). Nino.TypeScript generates TypeScript from the layouts the formatters are generated from, after each build,
structs and version-tolerant types included
(compared with MemoryPack's),
and Nino.AspNetCore adds MVC formatters and Minimal API bodies; browser clients can also run the same C# types on
.NET WebAssembly. Streams, async I/O, multi-segment
ReadOnlySequence<byte> input, callbacks and compression are a few lines around Nino's IBufferWriter<byte> and
ReadOnlySpan<byte> APIs; the full comparison has examples.
MemoryPack's union tag is 1 byte where Nino writes a 4-byte type id, and that is a trade: the id is derived from the
type, so subtypes need no tags, can be added in any assembly (a plugin or a HybridCLR hot update) and never renumber,
while every MemoryPack union lists or registers its subtypes by hand.
v4 is a rewrite of the runtime and the generator. Its binary format is not compatible with v3.
| v4 vs v3 (3.3.13) | |
|---|---|
| Serialize | 1.82× faster (class), 2.16× faster (class array), on par (struct array) |
| Deserialize | 1.16× faster (class), 1.19× faster (class array), on par (struct), on par (struct array), while adding reference tracking, reuse deserialization and payload validation |
| Compiling the generated code | 51× faster at 330 types (42.9 s → 0.84 s), 221× faster at 1,100 types (462 s → 2.1 s) |
| Running the generator | 3.1× faster; after an edit, only that type is re-generated (8.3× faster) |
| Generated code | 14× smaller source, 5× smaller IL |
New in v4, through 4.0.0-rc.6:
- Formats: version-tolerant and keyed types, reference tracking, and the wire layout of every type published as
[assembly: NinoTypeLayout]metadata, so a change in the bytes shows up between builds. - Allocation: reuse deserialization into existing objects,
[NinoPooled]pools,[NinoFactory], andNinoArrayBufferWriter, which can rent from your ownArrayPool<byte>. - Types: pluggable formatters (
[assembly: NinoFormatter(...)]), user collection types,BigInteger,Uri,Version,StringBuilder,BitArray,CultureInfo,TimeZoneInfo,Lazy<T>,Memory<T>,ReadOnlySequence<T>, read-only and concurrent collections,ILookup,BlockingCollection<T>,PriorityQueue(.NET 8+), every immutable collection (Nino.Immutable) and Unity'sAnimationCurve,GradientandRectOffset(Nino.UnityTypes, in the Unity package). - Speed: interceptors, inlined struct formatters, block copies for arrays and collections of raw structs,
tuples and
KeyValuePairs, and SIMD on AVX-512, AVX2, Arm64 and WebAssembly; releases are benchmarked on x64 and Arm64. - Platforms: identical bytes on every target framework, runtime and CPU, trimming and Native AOT annotations,
assemblies that unload with a collectible
AssemblyLoadContext(NinoRegistry.Removebefore .NET 8), and HybridCLR without an init call. - Strings: text whose chars are all at most U+00FF (ASCII, Latin-1) is written in one byte a char, other text as
UTF-16, and
[NinoUtf8]writes UTF-8 per member. - Integrations: Nino.AspNetCore (MVC formatters, Minimal API
NinoBody<T>andNinoResults) and Nino.TypeScript (TypeScript classes and codecs for browsers and Node, generated after each build). - Tooling: an incremental generator and 26 analyzers (NINO001–NINO026) with code fixes.
See what's new, the changelog and the migration guide; internals are in docs/v4-design.md.
Issues and pull requests are welcome. CONTRIBUTING.md covers building, the test matrix and releasing.
MIT License · Made by JasonXuDeveloper
