Library / SDK
protobuf-net/protobuf-net avatar
protobuf-net/protobuf-net

protobuf-net writes the wire format from C# attributes, not from a schema file

Protocol Buffers library for idiomatic .NET

4,975 stars1,090 forksC#NOASSERTION

At a glance

What is it?
protobuf-net turns ordinary .NET classes into protocol buffers data by tagging them with ProtoContract and ProtoMember instead of generating code from a .proto file. The parts worth understanding before you adopt it are the field number rules, the build tools that ship inside the package, and the runtimes it stops at.
Who is it for?
protobuf-net fits teams that already own their .NET type model and want the protobuf wire format without maintaining a separate schema and a code generation step. It does not fit a project where the schema has to be the one source of truth across several languages, or where a paid support contract is a procurement requirement.
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 1 day 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 November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • protobuf-net/protobuf-net4,975 ★ · +3
Star history
Repository2026-09-232026-11-14
protobuf-net/protobuf-net5k5k

3 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Install-Package protobuf-net is the whole install story

Every stable package, plus some pre-release ones, sits on NuGet, and the entire install story is one line in the Package Manager Console:

ps
Install-Package protobuf-net

That same package carries the build tools. Since version 3.3 the package ships analyzers that validate contracts plus a generator that emits serializers at build time, so there is no second package to add for native AOT or trimming. Switching them off is a project property rather than a package change: `<ProtoBufDisableBuildTools>true</ProtoBufDisableBuildTools>`.

The runtime table lists protobuf-net and nothing else. The gRPC and Connect pieces live in separate packages and their names are not in that table, so you look them up yourself. Type generation also happens outside your build, either through the multi-platform `protogen` global tool or in the browser at protobuf-net.dev, a static site that generates C# and VB types from a .proto schema and decodes a raw payload without the schema that produced it.

Attributes are the schema, so member names never reach the file

The first step is not code generation, it is decoration. A type that will be serialized has to say so, and every member that travels needs an integer:

csharp
[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;}
}

This is where protobuf-net parts company with XmlSerializer and DataContractSerializer in feel: member names are not encoded in the data, so a file holds integers and values only. The upside is that renaming a member costs nothing on the wire, and you can move a member between a property and a field. The downside is that nothing in the file explains itself. A reader holding a payload it cannot map onto a contract gets no hint from the bytes about which value belonged to which field, so the two contracts have to line up before serialization rather than after.

Field numbers decide both the size of your data and who can still read it

The integers you pick are the real schema, and the rules around them are strict:

csharp
[ProtoMember(1)]
public int Id {get;set;}
[ProtoMember(2)]
public string Name {get;set;}
[ProtoMember(3)]
public Address Address {get;set;}

They must be positive integers. For best portability they should sit at or below 536870911 and stay out of the `19000-19999` range. They must be unique within a single type, although the same numbers can be re-used in sub-types when inheritance is enabled, and they must not conflict with any inheritance identifier. Lower numbers take less space, so do not start at 100,000,000. The identifier is the part that matters: you can change the member name or shift it between a property and a field, but changing the identifier changes the data.

Inheritance is opt-in, one ProtoInclude per known sub-type

Polymorphism is never inferred. Each base type with known sub-types has to name them, the same way it would for XmlSerializer or DataContractSerializer:

csharp
[ProtoContract]
[ProtoInclude(7, typeof(SomeDerivedType))]
class SomeBaseType {...}

[ProtoContract]
class SomeDerivedType {...}

The 7 carries no special meaning. It is an integer key like every [ProtoMember(...)] attribute, it only has to be unique in terms of SomeBaseType, meaning no other ProtoInclude or ProtoMember on that type may use 7, and it does not have to be unique globally. That scoping is the trap. Two base types can each claim 7 without any complaint, while adding a ProtoMember(7) to the base type itself collides with the sub-type mapping. Because that uniqueness is scoped to one type, a shared base hierarchy spread across several assemblies needs a deliberate numbering scheme before the first type is written, otherwise the numbers collide later when nobody remembers the original choices.

A 32 byte file, and RuntimeTypeModel.Default behind the Serializer shortcuts

Serialization is two calls against a file:

csharp
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);
}

And reading it back:

csharp
Person newPerson;
using (var file = File.OpenRead("person.bin")) {
    newPerson = Serializer.Deserialize<Person>(file);
}

That example writes 32 bytes to person.bin, which shows how little of the payload is overhead once the names are gone. From version 2 onwards everything the attributes can express can also be configured at runtime through RuntimeTypeModel, and the Serializer methods are described as shortcuts to RuntimeTypeModel.Default. The moment you need behavior the attributes do not cover, RuntimeTypeModel.Default becomes the object you configure, and attributes alone stop being the whole story.

Build time generation moved the serializer cost from startup to compile

From version 3.3 the package can generate serializers at build time from code-first contracts, which is what makes native AOT and trimming work, and the project also credits it with better cold start on ordinary JIT builds. Two consequences follow. First, contract mistakes surface as analyzer diagnostics while the solution builds instead of as reflection failures on first use, so a project that upgrades and immediately deploys can meet a new diagnostic it has never seen before. Second, the package now carries a compiler component, and the opt-out removes the generator along with the analyzers:

xml
<ProtoBufDisableBuildTools>true</ProtoBufDisableBuildTools>

Setting that property to get a build through also turns off the contract-validating analyzers. Any team that reaches for it should record which contracts stopped being validated that way.

Where runtime support stops, and what Connect changes about gRPC

Supported targets are .NET 6.0 and later on modern .NET, .NET Standard 2.0 and 2.1, and .NET Framework 4.6.2 and later. .NET 5 and similar older runtimes fall back to the .NET Standard 2.1 build, so one package serves both generations. A second project, protobuf-net.Connect, implements the Connect protocol, which gives gRPC semantics over ordinary HTTP. The point of it is reach: unary and one-way streaming work over HTTP/1.1, through proxies and CDNs that will not carry gRPC, it reuses protobuf-net.Grpc contracts unchanged, and it is AOT-first. The failure to plan for is therefore infrastructure rather than features, because if something along your path only speaks HTTP/1.1 the usual gRPC client shape will not get through it.

Support is volunteer. The maintainer points at the protobuf-net tag on Stack Overflow, issues on GitHub and email, and states that there is no paid support channel. Licence.txt sits at the root of the repository next to SECURITY.md, and the documentation does not restate the terms, so anyone needing an indemnity has to read that file. The last commits landed on 30 October 2026, with release 3.4.40 published on 21 October 2026 after 3.4.29 and 3.4.30 on 15 and 16 September 2026.

Editorial conclusion

protobuf-net fits teams that already own their .NET type model and want the protobuf wire format without maintaining a separate schema and a code generation step. It does not fit a project where the schema has to be the one source of truth across several languages, or where a paid support contract is a procurement requirement. Before committing, build your real contracts with the analyzers that ship in the package, confirm that every field number already stored in your databases or queues stays exactly where it is, and check that your oldest target framework still resolves a build.

Frequently asked questions

What is protobuf-net?

A contract based serializer for .NET code that happens to write data in the protocol buffers serialization format. Its API follows typical .NET patterns and is broadly comparable in usage to XmlSerializer and DataContractSerializer, and it targets most .NET languages that write standard types and can use attributes.

How do I use protobuf-net?

Decorate each class with [ProtoContract] and each member with [ProtoMember(n)], then call Serializer.Serialize(file, person) to write a file and Serializer.Deserialize<Person>(file) to read it back. The worked example in the documentation writes 32 bytes to a file called person.bin.

What is protobuf-net vs MessagePack?

The documentation makes no comparison with MessagePack. It places protobuf-net next to XmlSerializer and DataContractSerializer as broadly comparable in usage, and it names the alternatives inside protobuf-net itself: attributes on your classes, a RuntimeTypeModel configured at runtime, or types generated from a .proto schema with protogen.

What is the protobuf-net DLL?

The repository root holds protobuf-net.slnx, Build.csproj and a src directory, and NuGet consumers add the library with Install-Package protobuf-net in the Package Manager Console. Per target framework assembly names are not enumerated in the documentation, so check the package contents yourself if your build needs a specific assembly path.

protobuf-net vs protobuf-net core?

On modern .NET the library targets .NET 6.0 and later, and .NET 5 and similar older runtimes use the .NET Standard 2.1 build. The package also covers .NET Standard 2.0 and .NET Framework 4.6.2 and later, so the same package reference works across both generations of runtime.

What is Protobuf used for?

Here it means writing .NET objects to a binary stream in the protocol buffers format: the sample Person and Address contract serializes to 32 bytes, and member names are absent from the data, leaving only the integers you assigned with [ProtoMember(n)] and the values themselves.

Official sources

  1. Issues
  2. Project website
  3. protobuf-net/protobuf-net on GitHub
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/protobuf-net-protobuf-net.svg)](https://hysenlabs.com/projects/protobuf-net-protobuf-net)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/protobuf-net-protobuf-net.svg)](https://hysenlabs.com/projects/protobuf-net-protobuf-net)

Was this useful?