protobuf-net: a .NET contract serializer that writes protobuf on the wire
Protocol Buffers library for idiomatic .NET
At a glance
- What is it?
- protobuf-net keeps .NET attribute-based serialization but encodes with Google's protobuf format. Here is how the contract model works, how to install and use it, and where it stops being the right tool.
- Who is it for?
- Adopt protobuf-net when you want protobuf's compact wire format but intend to keep .NET classes and attributes rather than generated .proto types, and when the 3.3+ build-time generator matters for native AOT or trimming.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem protobuf-net solves for .NET teams
Google's protobuf toolchain expects a .proto schema and generates types from it. That is a reasonable workflow, but it is not how most .NET codebases are written. They are written as classes with properties, and serialization is usually attached to those classes with attributes, in the style of XmlSerializer or DataContractSerializer. protobuf-net sits exactly in that gap: the README describes it as a contract-based serializer for .NET code that "happens to write data in the 'protocol buffers' serialization format", with an API that "follows typical .NET patterns". You keep your Person and Address classes, decorate them, and the bytes on the wire are protobuf.
The audience is therefore .NET developers who want protobuf's compact encoding and cross-language compatibility without adopting generated message classes as their domain model. It is a poor fit for anyone who wants the .proto file itself to be the artifact that other languages consume directly, because in the code-first path the schema lives in your C# attributes, not in a shared file. The README does mention a middle path: the protogen tool and the browser-based generator at protobuf-net.dev can produce C# or VB types from a .proto schema, so the schema-first workflow exists, it is just not the default story.
How the contract model and wire identifiers work
The mechanism is attribute-driven. A class is marked [ProtoContract] to signal that it is a data contract, and each member that should be serialized is marked [ProtoMember(n)] where n is an integer identifier. The README is explicit that member names are not encoded in the data, unlike XmlSerializer. The integer is the field identity. That single design decision drives most of the operational rules around the library.
Identifiers must be positive integers, and the README recommends keeping them at or below 536870911 and outside the range 19000-19999 for portability. They must be unique within a type, though the same numbers can be reused in sub-types when inheritance is enabled. Lower numbers take less space, so starting at 100,000,000 wastes bytes on every message. The README states the consequence of changing one plainly: you can rename a member or move it between a property and a field without breaking anything, but changing the identifier changes the data.
Inheritance is not inferred. It is declared with [ProtoInclude(key, typeof(Derived))] on the base type, and the key behaves like any other field number: it must be unique among the ProtoInclude and ProtoMember keys on that base type. The README notes it does not need to be globally unique. Everything attributes can express is also expressible at runtime through RuntimeTypeModel, and the Serializer methods are shortcuts to RuntimeTypeModel.Default, so configuration applied to the default model is what those shortcuts see.
Installing protobuf-net and serializing your first object
The runtime package is on NuGet. From the Package Manager Console the README gives this command:
Install-Package protobuf-netIn an SDK-style project you would add the same package reference through your usual tooling. Supported runtimes per the README are .NET 6.0+ (with .NET 5 and similar falling back to .NET Standard 2.1), .NET Standard 2.0 and 2.1, and .NET Framework 4.6.2+.
Once referenced, the first real use is a decorated class. The README's own example looks like this:
[ProtoContract]
class Person {
[ProtoMember(1)]
public int Id {get;set;}
[ProtoMember(2)]
public string Name {get;set;}
[ProtoMember(3)]
public Address Address {get;set;}
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 {get;set;}
[ProtoMember(2)]
public string Line2 {get;set;}
}Serializing an instance writes a 32 byte file to "person.bin" according to the README:
var person = new Person {
Id = 12345, Name = "Fred",
Address = new Address {
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
using (var file = File.Create("person.bin")) {
Serializer.Serialize(file, person);
}Reading it back is symmetric:
Person newPerson;
using (var file = File.OpenRead("person.bin")) {
newPerson = Serializer.Deserialize<Person>(file);
}What you should see is a small binary file and an object graph equal to the one you wrote. If the deserialized object comes back with default values, the usual cause is a member that was never marked [ProtoMember] or a contract that was never marked [ProtoContract].
Native AOT, trimming, and the build tools you get by default
From 3.3, protobuf-net can generate serializers at build time from code-first contracts. The README states this makes native AOT and trimming work and improves cold start even on ordinary JIT builds. This matters because reflection-based serialization is the classic reason a .NET library fails under trimming: the trimmer cannot see which types the serializer will touch. Moving generation to build time removes that dependency at runtime.
The build tools, described as analyzers that validate contracts plus the generator, ship inside the protobuf-net package by default. You opt out with a property in your project file:
<ProtoBufDisableBuildTools>true</ProtoBufDisableBuildTools>That default is worth thinking about. A serializer package that adds analyzers and a source generator to every consuming project is doing more than adding a runtime library, and the README does not describe a staged rollout, a warning level, or a documented rollback beyond the opt-out property. If your build pipeline is sensitive to analyzer load or generated code volume, plan for that property before you upgrade rather than after. The AOT documentation is pointed to at docs.protobuf-net.dev/aot; the README itself does not enumerate which contract shapes the generator supports.
protobuf-net versus Google.Protobuf and MessagePack
The most direct alternative is Google.Protobuf, the official C# runtime. The difference is where the schema lives. Google.Protobuf generates message classes from a .proto file, and those generated classes are what you serialize. protobuf-net serializes your own classes and records the schema in attributes. If several languages consume the same messages and the .proto file is the contract everyone negotiates over, the generated-class approach keeps that file authoritative and gives you no chance to drift from it. protobuf-net's code-first path inverts that: your C# types are authoritative, and a .proto file becomes an output you can produce with protogen when someone else needs one. The README also notes that protobuf-net's API is "very different to Google's", so this is not a drop-in swap in either direction.
MessagePack is the other comparison people make, and the distinction is the schema. MessagePack maps objects to a compact binary form without requiring field numbers, which is convenient for internal caches and short-lived payloads where you control both ends. protobuf requires you to assign and then respect those numbers, which is friction up front and compatibility discipline later. The payoff is that adding a field is safe as long as you add a new number and never reuse an old one. If your data never leaves one process family and you are willing to regenerate caches when the shape changes, that discipline buys you less. If the payload is a long-lived contract between services, it buys you a lot.
Where protobuf-net is the wrong choice
The identifier rules are the sharpest limitation. Because field numbers are the wire identity and member names are not stored, a schema change that renumbers a field silently makes old data unreadable by new code, and the README says as much without offering a migration tool. There is no documented compatibility checker in the README beyond the analyzers that validate contracts; validation is not the same as detecting that field 3 used to mean something else.
The identifier space also has constraints that bite in generated or inherited models. Numbers must be unique within a type, must not collide with ProtoInclude keys on the same base type, and should avoid 19000-19999. A codebase that assigns numbers by hand across many teams will eventually collide, and the failure appears at serialization time rather than at compile time unless the analyzers catch it.
The third boundary is the schema-first organization. If your team's process is that a .proto file is reviewed, versioned and used to generate clients in Java, Go and Python, protobuf-net's code-first default puts the source of truth somewhere those reviewers do not look. You can use protogen to generate C# from the .proto instead, but at that point you are using the library in its less central mode, and the README's framing of the project as contract-based .NET serialization is not the workflow you are running.
Maintenance, licensing and upgrade cost
The repository is not archived, and the last push was on 2026-09-18. Releases are frequent and closely spaced: 3.4.28 on 2026-09-15, 3.4.29 later the same day, and 3.4.30 on 2026-09-16. That cadence means patch upgrades are cheap to take but also that the version you pin can move within days, so pinning an exact version in your package reference is the practical way to keep builds reproducible.
The licence file is present at the repository root as Licence.txt, but the repository metadata reports the licence as NOASSERTION, meaning the platform could not classify it automatically. The README does not state licence terms, and the text of Licence.txt is not reproduced in the README. Read that file before you ship the package in a product; this is a factual gap, not a legal opinion.
Upgrade cost has two components. The runtime package is a normal NuGet dependency. The build tools are not: they are included by default and add analyzers and a build-time generator to your compilation, which is the part most likely to interact with your existing build configuration. The README's only stated control is ProtoBufDisableBuildTools. Support is community-oriented; the README points to Stack Overflow under the protobuf-net tag, GitHub issues, and email, and states that no paid support channel is currently offered.
Editorial conclusion
Adopt protobuf-net when you want protobuf's compact wire format but intend to keep .NET classes and attributes rather than generated .proto types, and when the 3.3+ build-time generator matters for native AOT or trimming. Do not adopt it if your team treats a .proto file as the single source of truth and expects the schema to drive every language, or if you need a documented rollback path for the build tools: the README only shows ProtoBufDisableBuildTools as a compile-time opt-out. Before committing, verify that your member numbering survives the first schema change, because the identifier is the field identity and renaming a property is safe while renumbering is not.
Frequently asked questions
How do I use protobuf-net in a .NET project?
Install the protobuf-net package from NuGet, mark your classes with [ProtoContract], mark each serialized member with [ProtoMember(n)] using a unique positive integer, then call Serializer.Serialize and Serializer.Deserialize<T>. The README's example writes a Person with a nested Address to a file and reads it back.
What is protobuf-net?
It is a contract-based serializer for .NET that writes data in Google's protocol buffers format while keeping an API that follows typical .NET patterns, comparable in usage to XmlSerializer or DataContractSerializer. Member names are not encoded in the data; integer identifiers are.
What is the protobuf-net DLL?
The README does not describe the contents of a specific DLL file. What it documents is the NuGet package protobuf-net, which contains the runtime serializer plus build tools (analyzers and a build-time generator) that can be disabled with the ProtoBufDisableBuildTools property.
How does protobuf-net compare with MessagePack?
protobuf-net requires you to assign integer field identifiers and keep them stable, which is what makes adding fields compatible over time. MessagePack-style serialization does not require that numbering discipline. The trade-off is compatibility discipline versus up-front convenience, and the README does not benchmark the two.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/protobuf-net-protobuf-net)